Skip to content

Create apps directly from the terminal#6

Merged
hanrw merged 15 commits intomainfrom
iris
Mar 21, 2026
Merged

Create apps directly from the terminal#6
hanrw merged 15 commits intomainfrom
iris

Conversation

@hanrw
Copy link
Member

@hanrw hanrw commented Mar 21, 2026

This pull request introduces a major new feature: support for the App Store Connect "iris" private API, enabling cookie-based authentication and new commands for app management. It adds a new asc iris command namespace, implements cookie extraction from browsers or environment variables, and provides the ability to list and create apps via the private API. The implementation includes new domain models, repositories, infrastructure, and documentation.

Iris Private API Support

  • Added new asc iris command namespace with subcommands for interacting with the App Store Connect private API using browser cookies (IrisCommand, IrisStatus, IrisAppsCommand, IrisAppsList, IrisAppsCreate) [1] [2] [3] [4] [5].
  • Implemented cookie-based authentication, automatically extracting cookies from Chrome/Safari/Firefox via the new SweetCookieKit dependency, with CI/CD fallback using the ASC_IRIS_COOKIES environment variable [1] [2] [3] [4].

Domain and Infrastructure Additions

  • Introduced new domain models and protocols for the iris API, including AppBundle, IrisSession, IrisStatus, IrisCookieProvider, and IrisAppBundleRepository [1] [2] [3] [4] [5].
  • Added infrastructure for iris API access, including implementations for repositories and cookie providers, and a JSON:API request builder for app creation (IrisSDKAppBundleRepository, BrowserIrisCookieProvider, AppCreateRequest) [1] [2].

Client and Command Integration

  • Updated the command registration to include the new IrisCommand and provided access to iris repositories and cookie providers via ClientProvider [1] [2].

Documentation Updates

  • Updated README.md and CHANGELOG.md to document the new iris features, usage examples, and authentication flow [1] [2] [3] [4].

Iris Private API Command Support

  • Added asc iris command namespace with subcommands for checking session status, listing apps, and creating apps using the iris private API. [1] [2] [3] [4] [5]
  • Implemented browser cookie extraction for authentication, using the SweetCookieKit dependency, and fallback to environment variable for CI/CD. [1] [2] [3] [4]

Domain and Infrastructure Enhancements

  • Introduced domain models and protocols for iris entities and authentication (AppBundle, IrisSession, IrisStatus, IrisCookieProvider, IrisAppBundleRepository). [1] [2] [3] [4] [5]
  • Added infrastructure implementations for iris repositories and cookie providers, plus a JSON:API builder for app creation. [1] [2]

Integration and Registration

  • Registered new iris commands and provided access to iris repositories and cookie providers in ClientProvider and command entry points. [1] [2]

Documentation

  • Updated README.md and CHANGELOG.md with iris feature documentation, usage instructions, and authentication details. [1] [2] [3] [4]

Summary by CodeRabbit

  • New Features

    • Added Iris (private API) CLI namespace (asc iris) with status, apps list, and apps create (multi-platform app creation).
    • Automatic browser cookie authentication with an environment-variable fallback for CI.
  • Web

    • Integrated Iris UI into the command-center for status, app listing, and create workflows.
  • Documentation

    • Added docs and homepage feature entries with localized guides across site languages.

hanrw added 12 commits March 21, 2026 11:56
- Implement IrisClient for HTTP requests to iris API
- Add IrisSession and IrisCookieProvider protocols and models
- Provide BrowserIrisCookieProvider for cookie extraction from browsers/env
- Add IrisAppBundleRepository for app bundle operations
- Implement IrisSDKAppBundleRepository for API interactions
- Add new commands: apps iris-list and apps create via iris API
- Integrate SweetCookieKit for browser cookie access
- Include comprehensive tests for iris domain and commands
- Add version option to apps:create command with default "1.0"
- Extend IrisAppBundleRepository to accept versionString parameter
- Update IrisSDKAppBundleRepository to build JSON:API compound document including appStoreVersions and local
- Introduce AppCreateRequest and related types to build compound request
- Refactor IrisSDKAppBundleRepository to use AppCreateRequest factory
- Add comprehensive tests covering encoding and included relationships
- Query both apple.com and appstoreconnect.apple.com domains
- Aggregate relevant cookies into a map for cleaner lookup
- Return joined cookie string only if essential myacinfo cookie exists
- introduce iris command with status and apps subcommands
- add IrisStatus model and command to show cookie session info
- implement IrisAppsCommand with list and create subcommands
- rename and update tests to reflect iris command structure
- extend IrisCookieProvider to support status resolution
- enhance BrowserIrisCookieProvider to report cookie source and count
- Introduce `asc iris` namespace with `status`, `apps list`, and `apps create` commands
- Use cookie-based authentication via browser extraction or `ASC_IRIS_COOKIES` env var for CI/CD
- Support app creation with multi-platform and locale options
- Add SweetCookieKit dependency for automatic browser cookie retrieval
- Introduce Iris section in navigation and mock data
- Implement Iris status and apps list with create app support
- Display session info and app details with error handling
- Introduce Iris feature with icon, title, and description in all i18n JSON files
- Add Iris feature card with usage example to homepage content in all supported languages
- Update sitemap lastmod dates to reflect new content addition
@coderabbitai
Copy link

coderabbitai bot commented Mar 21, 2026

📝 Walkthrough

Walkthrough

Adds an "iris" CLI namespace with subcommands to check cookie-session status, list app bundles, and create apps. Implements cookie-based auth (browser extraction via SweetCookieKit or ASC_IRIS_COOKIES), domain models, HTTP client, repository, CLI wiring, tests, docs, and web UI pages/translation updates.

Changes

Cohort / File(s) Summary
Package / Dependencies
Package.swift, Package.resolved
Added sweetcookiekit dependency (manifest and resolved pins).
CLI Registration & Providers
Sources/ASCCommand/ASC.swift, Sources/ASCCommand/ClientProvider.swift, Sources/Infrastructure/Client/ClientFactory.swift
Registered IrisCommand and added factory methods makeIrisCookieProvider() and makeIrisAppBundleRepository() to expose Iris dependencies.
Commands
Sources/ASCCommand/Commands/Iris/... (IrisCommand.swift, IrisStatus.swift, IrisAppsCommand.swift, IrisAppsList.swift, IrisAppsCreate.swift)
Added iris root and subcommands: status, apps list, apps create; each builds providers, resolves sessions/status, calls repo, and formats output.
Domain: Iris Models & Protocols
Sources/Domain/Iris/* (AppBundle.swift, IrisSession.swift, IrisStatus.swift, IrisAppBundleRepository.swift, IrisCookieProvider.swift)
Introduced AppBundle, IrisSession, IrisStatus + IrisCookieSource; added IrisCookieProvider and IrisAppBundleRepository protocols (mockable).
Infrastructure: Iris HTTP & Repo
Sources/Infrastructure/Iris/* (IrisClient.swift, IrisCreateAppDocument.swift, IrisSDKAppBundleRepository.swift)
Added IrisClient HTTP layer with error handling, JSON:API create-request document model, and IrisSDKAppBundleRepository implementing list/create via Iris endpoints.
Infrastructure: Cookie Provider
Sources/Infrastructure/Iris/BrowserIrisCookieProvider.swift
Implemented BrowserIrisCookieProvider that reads ASC_IRIS_COOKIES or extracts cookies from browsers via SweetCookieKit; exposes session/status and error type.
Tests
Tests/.../Iris/*, Tests/DomainTests/Iris/*, Tests/InfrastructureTests/Iris/*, Tests/DomainTests/TestHelpers/MockRepositoryFactory.swift
Added unit/integration tests for commands, models, request document encoding, client errors, cookie error, and repo mapping with mocks and expected outputs.
Web UI: Command Center & Console
apps/asc-web/command-center/js/..., apps/asc-web/command-center/index.html, apps/asc-web/console/js/presentation/nav-data.js, apps/asc-web/shared/infrastructure/mock-data.js
Added Iris page, navigation entry, sidebar, loaders, and mock data for status and apps; wired affordances for creating apps.
Docs & README
CHANGELOG.md, README.md, docs/features/iris.md, apps/asc-web/ARCHITECTURE.md
Documented Iris feature, cookie-auth flow, API shapes, and architecture; updated README and changelog.
Homepage & Translations
homepage/* (template, per-language index.html, i18n/*.json, sitemap.xml)
Added Iris feature card and i18n entries across locales; updated sitemap lastmod timestamps.
CI / Coverage / Repo Metadata
.github/workflows/ci.yml, codecov.yml, .github/ISSUE_TEMPLATE/*
Updated CI coverage ignore list for new files, codecov exclusions, and added issue templates.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant CLI as ASC CLI
participant CookieProv as BrowserIrisCookieProvider
participant Repo as IrisSDKAppBundleRepository
participant Client as IrisClient
participant API as AppStoreConnect Iris API

CLI->>CookieProv: resolveSession() / resolveStatus()
CookieProv-->>CLI: IrisSession / IrisStatus
CLI->>Repo: listAppBundles(session) or createApp(session, ...)
Repo->>Client: GET / POST with cookies
Client->>API: HTTP request (iris/v1...)
API-->>Client: HTTP response (JSON:API)
Client-->>Repo: (Data, HTTPURLResponse)
Repo-->>CLI: AppBundle(s)
CLI->>Output: format & print

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐇 I nibbled cookies from browser jars,

Found Iris keys and little stars;
Apps now sprout with a joyful hop,
From session check to server stop—
A rabbit’s cheer for code that bops!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Create apps directly from the terminal' accurately reflects the primary feature added—the ability to create App Store Connect apps via the new asc iris apps create command, which is the main user-facing capability.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch iris

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (9)
Tests/ASCCommandTests/Commands/Iris/IrisAppsListTests.swift (1)

30-49: Prefer semantic JSON assertions over full pretty-string equality.

The current assertion is fragile to harmless output formatting/key-order changes. Parse JSON and assert fields instead.

♻️ Suggested assertion refactor
+import Foundation
 import Mockable
 import Testing
 `@testable` import ASCCommand
 `@testable` import Domain
@@
-        `#expect`(output == """
-        {
-          "data" : [
-            {
-              "affordances" : {
-                "listAppInfos" : "asc app-infos list --app-id app-1",
-                "listVersions" : "asc versions list --app-id app-1"
-              },
-              "bundleId" : "com.example.app",
-              "id" : "app-1",
-              "name" : "My App",
-              "platforms" : [
-                "IOS"
-              ],
-              "primaryLocale" : "en-US",
-              "sku" : "SKU1"
-            }
-          ]
-        }
-        """)
+        let bytes = try `#require`(output.data(using: .utf8))
+        let root = try `#require`(JSONSerialization.jsonObject(with: bytes) as? [String: Any])
+        let data = try `#require`(root["data"] as? [[String: Any]])
+        `#expect`(data.count == 1)
+
+        let first = try `#require`(data.first)
+        `#expect`(first["id"] as? String == "app-1")
+        `#expect`(first["name"] as? String == "My App")
+        `#expect`(first["bundleId"] as? String == "com.example.app")
+        `#expect`(first["sku"] as? String == "SKU1")
+
+        let affordances = try `#require`(first["affordances"] as? [String: String])
+        `#expect`(affordances["listVersions"] == "asc versions list --app-id app-1")
+        `#expect`(affordances["listAppInfos"] == "asc app-infos list --app-id app-1")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/ASCCommandTests/Commands/Iris/IrisAppsListTests.swift` around lines 30
- 49, Replace the fragile string-equality assertion in IrisAppsListTests.swift
(the `#expect`(output == """...""") block) with semantic JSON assertions: parse
output into a JSON/dictionary, assert that "data" is an array of count 1, then
assert properties on data[0] (id == "app-1", bundleId == "com.example.app", name
== "My App", platforms contains "IOS", primaryLocale == "en-US", sku == "SKU1")
and assert affordances.listAppInfos == "asc app-infos list --app-id app-1" and
affordances.listVersions == "asc versions list --app-id app-1"; this removes
dependency on whitespace/order while keeping the same intent.
Tests/ASCCommandTests/Commands/Iris/IrisStatusTests.swift (1)

34-44: Consider a structured assertion instead of substring matching.

contains(...) is permissive; decoding/parsing and asserting the source field would reduce false positives in future output changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/ASCCommandTests/Commands/Iris/IrisStatusTests.swift` around lines 34 -
44, Replace the brittle substring assertion with a structured decode and
equality check: after executing ASCCommand.IrisStatus (created via
ASCCommand.IrisStatus.parse and executed with mockCookieProvider which has
resolveStatus stubbed), parse the output JSON into the Iris status model
(Domain.IrisStatus or a minimal Decodable struct matching the "source" field)
and assert that decoded.source == .environment (or equals "environment" if using
a string-backed enum); this uses the same cmd.execute and mock setup but
validates the source field directly instead of using output.contains(...).
apps/asc-web/command-center/js/presentation/pages/iris.js (2)

40-49: Consider logging errors for debugging.

The empty catch block silently swallows errors. While the UI shows a fallback "No iris session" card, logging the error would help with debugging.

💡 Suggestion
-  } catch {
+  } catch (err) {
+    console.warn('Iris status check failed:', err);
     document.getElementById('irisStatus').innerHTML = `
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/asc-web/command-center/js/presentation/pages/iris.js` around lines 40 -
49, The catch block in the iris session fetch should not be empty; change the
anonymous catch to accept the error (catch (err) or catch (e)) and log the error
before rendering the "No iris session" UI—e.g., use console.error or the app's
logger—so in the catch handling for the code that updates
document.getElementById('irisStatus').innerHTML you capture and log the
exception (reference the catch block around the irisStatus update) to aid
debugging while keeping the existing fallback UI.

80-82: Hardcoded create modal uses placeholder values.

showIrisCreateModal() runs a command with hardcoded "My App", com.example.app, and MYSKU. This appears intentional as a quick-start demo, but users may expect input prompts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/asc-web/command-center/js/presentation/pages/iris.js` around lines 80 -
82, The showIrisCreateModal function currently calls runAffordance with
hardcoded placeholders ("My App", com.example.app, MYSKU); change it to collect
real input from the user (e.g., a small modal or prompt flow) for app name,
bundle id and SKU, then build the command string using those collected values
and pass that to window.runAffordance; update the function (showIrisCreateModal)
and any helper that opens the modal so defaults can be provided but not
hardcoded into the runAffordance invocation.
Sources/Infrastructure/Iris/AppBundles/IrisCreateAppDocument.swift (2)

20-28: Inner type Data shadows Swift.Data.

Naming the inner struct Data shadows the Foundation/Swift Data type. While it works due to scoping, it can cause confusion when reading or maintaining the code. Consider renaming to RequestData or DocumentData.

♻️ Suggested rename
 struct AppCreateRequest: Encodable {
-    let data: Data
+    let data: RequestData
     let included: [IncludedResource]

-    struct Data: Encodable {
+    struct RequestData: Encodable {
         let type: String
         let attributes: Attributes
         let relationships: Relationships
     }

Then update line 126:

-            data: Data(
+            data: RequestData(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Infrastructure/Iris/AppBundles/IrisCreateAppDocument.swift` around
lines 20 - 28, The inner struct named Data shadows Swift's Foundation.Data;
rename the inner type to RequestData (or DocumentData) and update all references
in this file: change AppCreateRequest's property declaration from let data: Data
to let data: RequestData and rename struct Data to struct RequestData; also
update any use sites that reference AppCreateRequest.Data (for example usages
near where Attributes/Relationships are referenced) so they point to
AppCreateRequest.RequestData to avoid the shadowing and retain correct scoping.

51-60: Attributes typed as [String: String]? limits flexibility.

If the iris API ever requires non-string attribute values (e.g., booleans, numbers), this type won't accommodate them. For now this works, but consider using a more flexible type like [String: AnyCodable] or a custom AttributeValue enum if needed in the future.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Infrastructure/Iris/AppBundles/IrisCreateAppDocument.swift` around
lines 51 - 60, The IncludedResource struct currently types attributes as
[String: String]?, which prevents non-string values; update IncludedResource
(and any usages) so attributes use a flexible encodable type (e.g., [String:
AnyCodable]? or a custom AttributeValue enum that conforms to Encodable) and
ensure IncludedResource remains Encodable; if you choose AnyCodable add or
import the AnyCodable helper and change the attributes property, or implement
AttributeValue enum (cases for string, number, bool, null, object) and update
encoding/decoding logic accordingly so IncludedResource.attributes can represent
numbers/booleans/objects as well as strings.
Sources/Infrastructure/Iris/IrisClient.swift (3)

39-39: Force-unwrap on URL construction.

Same concern as the GET method—if path contains invalid characters, this will crash.

🛡️ Suggested fix
-        let url = URL(string: "\(Self.baseURL)/\(path)")!
+        guard let url = URL(string: "\(Self.baseURL)/\(path)") else {
+            throw IrisAPIError.invalidResponse
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Infrastructure/Iris/IrisClient.swift` at line 39, The URL is
force-unwrapped when building let url = URL(string: "\(Self.baseURL)/\(path)")!,
which can crash for invalid path characters; change this to safely construct and
handle failure: build the URL using URLComponents or percent-encode the path
segment (use Self.baseURL and path) and replace the force-unwrap with a guard
let url = URL(...) else { return/throw an appropriate error }, so the calling
HTTP method (the same function that currently declares url) returns a failure
instead of crashing.

51-55: Hardcoded user-agent and CSRF token may require future updates.

The Chrome version string (146.0.0.0) and the CSRF header value ([asc-ui]) are hardcoded. If Apple's API changes its validation logic or blocks outdated user-agents, this will require code changes. Consider extracting these as configurable constants or injectable parameters for easier maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Infrastructure/Iris/IrisClient.swift` around lines 51 - 55, Extract
the hardcoded user-agent and CSRF header values used when configuring the
request in IrisClient.swift into configurable constants or injectable parameters
so they can be updated without changing code; locate the code that calls
request.setValue(..., forHTTPHeaderField: "user-agent") and
request.setValue(..., forHTTPHeaderField: "x-csrf-itc") and replace the literal
strings with references to a configuration (e.g., constants on IrisClient or
init-injected properties like userAgent and csrfTokenValue) and provide sensible
defaults while allowing overrides for tests or runtime configuration.

23-27: Force-unwraps may crash on malformed paths.

If path contains characters that cannot form a valid URL (e.g., unescaped special characters), the force-unwrap on line 23 or 27 will crash. Consider using guard let with a thrown error for safer handling.

🛡️ Suggested defensive handling
-        var components = URLComponents(string: "\(Self.baseURL)/\(path)")!
-        if !queryItems.isEmpty {
-            components.queryItems = queryItems
-        }
-        var request = URLRequest(url: components.url!)
+        guard var components = URLComponents(string: "\(Self.baseURL)/\(path)"),
+              let url = ({ 
+                  if !queryItems.isEmpty { components.queryItems = queryItems }
+                  return components.url 
+              }()) else {
+            throw IrisAPIError.invalidResponse
+        }
+        var request = URLRequest(url: url)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Infrastructure/Iris/IrisClient.swift` around lines 23 - 27, The code
in IrisClient that builds the request using URLComponents(string:
"\(Self.baseURL)/\(path)") and then force-unwraps components.url for URLRequest
can crash on malformed paths; update this in the request construction (where
URLComponents and URLRequest are used) to safely unwrap with guard let (or
try/throw) — validate URLComponents(string:) and components.url, return or throw
a descriptive error (e.g., invalidURLError) from the request-building function
when unwrapping fails, and ensure callers handle that error instead of relying
on force-unwraps of baseURL/path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/asc-web/command-center/js/presentation/pages/iris.js`:
- Around line 73-74: The template is inserting unescaped user-controlled values:
in the platforms mapping and app.id; wrap each platform string and the app.id
with the HTML-escaping helper (e.g., escapeHTML) before injecting into the
template to prevent XSS—use escapeHTML(p) inside the map when creating the <span
class="platform-badge"> and use escapeHTML(app.id) for the cell-mono output (and
import/ensure escapeHTML is available in iris.js if not).

In `@apps/asc-web/shared/infrastructure/mock-data.js`:
- Around line 245-258: The mock objects irisStatus and irisApps lack the
affordances field present in real Iris responses; update the mock entries for
irisStatus and irisApps to include an affordances property that mirrors the real
CLI payload (e.g., an array of affordance objects with rel, href, method and
optional body/schema) so command-center in mock mode can see next-step commands;
modify the irisStatus data item and each irisApps.data item to include
appropriate affordances (for example affordances for refresh/status-check on
irisStatus and for update/fetch/details on irisApps) keeping existing keys (id,
name, bundleId, etc.) intact.

In `@CHANGELOG.md`:
- Around line 10-17: The [Unreleased] section currently only has "### Added";
add missing "### Changed" and "### Fixed" headings under the same [Unreleased]
block (adjacent to the existing "### Added") and include either relevant entries
or placeholder lines (e.g., "N/A" or "None") under each to satisfy the
repository's Keep a Changelog format; ensure the headings are titled exactly
"### Changed" and "### Fixed" to match guidelines and maintain the existing
markdown structure.

In `@docs/features/iris.md`:
- Around line 269-282: The code block showing the directory tree starting with
"Tests/" is missing a language identifier; update the fenced code block by
changing the opening triple backticks to include the text language identifier
(i.e., replace ``` with ```text) so the directory tree (Tests/,
DomainTests/Iris/, IrisSessionTests.swift, etc.) is rendered correctly as plain
text in docs/features/iris.md.
- Around line 39-43: Update the fenced code block showing the table beginning
with "Source   Cookies" to include a language identifier such as `text` or
`plaintext` after the opening backticks so the block becomes "```text"; locate
the fenced block that contains the lines "Source   Cookies", "------  -------",
"browser  5" and add the identifier to the opening fence.
- Around line 244-265: The directory tree code block that starts with "Sources/"
in the Iris feature doc needs a language identifier; update the fenced code
block from ``` to ```text so the directory listing is rendered as plain text
(i.e., change the opening fence for the block containing the "Sources/" tree to
```text).
- Around line 156-176: Add the language identifier "text" to the fenced code
block containing the ASCII architecture diagram so the diagram is marked as text
(change the opening triple-backtick to "```text"); locate the ASCII diagram
block (the diagram starting with
"┌─────────────────────────────────────────────────────────────┐" and the
triple-backtick fence) and update only the opening fence to include text to
satisfy linting and improve rendering.

In `@homepage/i18n/zh.json`:
- Line 147: Update the "description" value for the App Store Connect entry (the
"description" JSON key) to avoid the absolute phrase "无需额外设置"; instead clarify
that browser-based usage uses cookie-based auth but CI/headless environments
still require providing ASC_IRIS_COOKIES (or equivalent env var) for Iris auth;
edit the sentence to contrast local browser flow vs CI/headless flow and mention
ASC_IRIS_COOKIES explicitly.

In `@homepage/index.html`:
- Around line 343-345: Update the copy in the feature card for "Iris (Private
API)" by editing the <h3> "Iris (Private API)" card's <p
class="feature-card-description"> so it no longer claims app listing is
unavailable via the public REST API; instead state that while apps can be listed
via the public GET /v1/apps endpoint, Iris unlocks private-API flows such as
cookie-based auth/backed app creation and Iris-specific private-session
workflows. Locate the paragraph text under the "Iris (Private API)" heading and
replace the inaccurate sentence about listing apps with the corrected phrasing
describing the real Iris capabilities.

In `@Package.resolved`:
- Around line 22-30: The Package.resolved entry for the dependency with
"identity" : "sweetcookiekit" is pinned to a non-existent version "0.4.0";
update that entry to the real release "0.3.0" by changing the "version" value to
"0.3.0" and also update the "revision" field to the commit hash that corresponds
to the SweetCookieKit v0.3.0 tag (or run swift package resolve / regenerate
Package.resolved to automatically populate the correct revision). Locate the
object with identity "sweetcookiekit" and apply these changes so dependency
resolution succeeds.

In `@Sources/Domain/Iris/IrisSession.swift`:
- Around line 6-7: IrisSession currently only conforms to Sendable and
Equatable; update the public struct IrisSession to also conform to Codable and
AffordanceProviding, add a public let affordances: [Affordance] property (or the
concrete affordance type used in the repo) and implement the required
AffordanceProviding requirements so the model exposes ready-to-run CLI commands;
ensure the struct signature (IrisSession) and its initializer remain stable and
that the affordances field is populated with the appropriate command definitions
expected by the Domain (use existing Affordance/CLI helpers elsewhere as a
reference).

In `@Sources/Infrastructure/Iris/AppBundles/IrisSDKAppBundleRepository.swift`:
- Around line 15-18: The request currently hardcodes URLQueryItem(name: "limit",
value: "300") in IrisSDKAppBundleRepository's fetch method (or wherever
queryItems are assembled), which will truncate results for accounts >300 apps;
update the implementation to page through results (use the API's pagination
cursor/offset and loop/recursively fetch until no next page) or at minimum
replace the hardcoded limit with a configurable constant and add a TODO noting
full pagination is required; reference the queryItems construction in
IrisSDKAppBundleRepository (and any associated fetchAppBundles / listAppBundles
method) so you implement paginated requests and merge results before returning.

In `@Sources/Infrastructure/Iris/BrowserIrisCookieProvider.swift`:
- Around line 18-22: The cookie count in resolveStatus() is computed with
cookies.components(separatedBy: "; "), which miscounts when separators lack a
space; update resolveStatus() to robustly parse the raw cookies returned by
resolve() by splitting on ";" (not "; "), trimming whitespace from each segment
with trimmingCharacters(in: .whitespacesAndNewlines), filtering out empty
segments, and using the resulting array's count when constructing
IrisStatus(cookieCount:); reference resolveStatus(), resolve(), and IrisStatus
in your change.

In `@Tests/DomainTests/Iris/IrisStatusTests.swift`:
- Around line 13-16: The test `status from environment shows source` creates
IrisStatus(source: .environment, cookieCount: 3) but only asserts
`status.source`; add an assertion to also verify `status.cookieCount == 3` in
that test (same pattern used in the browser case) so both source and cookieCount
are validated for the environment path.

---

Nitpick comments:
In `@apps/asc-web/command-center/js/presentation/pages/iris.js`:
- Around line 40-49: The catch block in the iris session fetch should not be
empty; change the anonymous catch to accept the error (catch (err) or catch (e))
and log the error before rendering the "No iris session" UI—e.g., use
console.error or the app's logger—so in the catch handling for the code that
updates document.getElementById('irisStatus').innerHTML you capture and log the
exception (reference the catch block around the irisStatus update) to aid
debugging while keeping the existing fallback UI.
- Around line 80-82: The showIrisCreateModal function currently calls
runAffordance with hardcoded placeholders ("My App", com.example.app, MYSKU);
change it to collect real input from the user (e.g., a small modal or prompt
flow) for app name, bundle id and SKU, then build the command string using those
collected values and pass that to window.runAffordance; update the function
(showIrisCreateModal) and any helper that opens the modal so defaults can be
provided but not hardcoded into the runAffordance invocation.

In `@Sources/Infrastructure/Iris/AppBundles/IrisCreateAppDocument.swift`:
- Around line 20-28: The inner struct named Data shadows Swift's
Foundation.Data; rename the inner type to RequestData (or DocumentData) and
update all references in this file: change AppCreateRequest's property
declaration from let data: Data to let data: RequestData and rename struct Data
to struct RequestData; also update any use sites that reference
AppCreateRequest.Data (for example usages near where Attributes/Relationships
are referenced) so they point to AppCreateRequest.RequestData to avoid the
shadowing and retain correct scoping.
- Around line 51-60: The IncludedResource struct currently types attributes as
[String: String]?, which prevents non-string values; update IncludedResource
(and any usages) so attributes use a flexible encodable type (e.g., [String:
AnyCodable]? or a custom AttributeValue enum that conforms to Encodable) and
ensure IncludedResource remains Encodable; if you choose AnyCodable add or
import the AnyCodable helper and change the attributes property, or implement
AttributeValue enum (cases for string, number, bool, null, object) and update
encoding/decoding logic accordingly so IncludedResource.attributes can represent
numbers/booleans/objects as well as strings.

In `@Sources/Infrastructure/Iris/IrisClient.swift`:
- Line 39: The URL is force-unwrapped when building let url = URL(string:
"\(Self.baseURL)/\(path)")!, which can crash for invalid path characters; change
this to safely construct and handle failure: build the URL using URLComponents
or percent-encode the path segment (use Self.baseURL and path) and replace the
force-unwrap with a guard let url = URL(...) else { return/throw an appropriate
error }, so the calling HTTP method (the same function that currently declares
url) returns a failure instead of crashing.
- Around line 51-55: Extract the hardcoded user-agent and CSRF header values
used when configuring the request in IrisClient.swift into configurable
constants or injectable parameters so they can be updated without changing code;
locate the code that calls request.setValue(..., forHTTPHeaderField:
"user-agent") and request.setValue(..., forHTTPHeaderField: "x-csrf-itc") and
replace the literal strings with references to a configuration (e.g., constants
on IrisClient or init-injected properties like userAgent and csrfTokenValue) and
provide sensible defaults while allowing overrides for tests or runtime
configuration.
- Around line 23-27: The code in IrisClient that builds the request using
URLComponents(string: "\(Self.baseURL)/\(path)") and then force-unwraps
components.url for URLRequest can crash on malformed paths; update this in the
request construction (where URLComponents and URLRequest are used) to safely
unwrap with guard let (or try/throw) — validate URLComponents(string:) and
components.url, return or throw a descriptive error (e.g., invalidURLError) from
the request-building function when unwrapping fails, and ensure callers handle
that error instead of relying on force-unwraps of baseURL/path.

In `@Tests/ASCCommandTests/Commands/Iris/IrisAppsListTests.swift`:
- Around line 30-49: Replace the fragile string-equality assertion in
IrisAppsListTests.swift (the `#expect`(output == """...""") block) with semantic
JSON assertions: parse output into a JSON/dictionary, assert that "data" is an
array of count 1, then assert properties on data[0] (id == "app-1", bundleId ==
"com.example.app", name == "My App", platforms contains "IOS", primaryLocale ==
"en-US", sku == "SKU1") and assert affordances.listAppInfos == "asc app-infos
list --app-id app-1" and affordances.listVersions == "asc versions list --app-id
app-1"; this removes dependency on whitespace/order while keeping the same
intent.

In `@Tests/ASCCommandTests/Commands/Iris/IrisStatusTests.swift`:
- Around line 34-44: Replace the brittle substring assertion with a structured
decode and equality check: after executing ASCCommand.IrisStatus (created via
ASCCommand.IrisStatus.parse and executed with mockCookieProvider which has
resolveStatus stubbed), parse the output JSON into the Iris status model
(Domain.IrisStatus or a minimal Decodable struct matching the "source" field)
and assert that decoded.source == .environment (or equals "environment" if using
a string-backed enum); this uses the same cmd.execute and mock setup but
validates the source field directly instead of using output.contains(...).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a830acb6-01d0-4014-a37f-6d68e2a7e616

📥 Commits

Reviewing files that changed from the base of the PR and between dff2413 and 05581d8.

📒 Files selected for processing (62)
  • CHANGELOG.md
  • Package.resolved
  • Package.swift
  • README.md
  • Sources/ASCCommand/ASC.swift
  • Sources/ASCCommand/ClientProvider.swift
  • Sources/ASCCommand/Commands/Iris/IrisAppsCommand.swift
  • Sources/ASCCommand/Commands/Iris/IrisAppsCreate.swift
  • Sources/ASCCommand/Commands/Iris/IrisAppsList.swift
  • Sources/ASCCommand/Commands/Iris/IrisCommand.swift
  • Sources/ASCCommand/Commands/Iris/IrisStatus.swift
  • Sources/Domain/Iris/AppBundles/AppBundle.swift
  • Sources/Domain/Iris/AppBundles/IrisAppBundleRepository.swift
  • Sources/Domain/Iris/IrisCookieProvider.swift
  • Sources/Domain/Iris/IrisSession.swift
  • Sources/Domain/Iris/IrisStatus.swift
  • Sources/Infrastructure/Client/ClientFactory.swift
  • Sources/Infrastructure/Iris/AppBundles/IrisCreateAppDocument.swift
  • Sources/Infrastructure/Iris/AppBundles/IrisSDKAppBundleRepository.swift
  • Sources/Infrastructure/Iris/BrowserIrisCookieProvider.swift
  • Sources/Infrastructure/Iris/IrisClient.swift
  • Tests/ASCCommandTests/Commands/Iris/IrisAppsCreateTests.swift
  • Tests/ASCCommandTests/Commands/Iris/IrisAppsListTests.swift
  • Tests/ASCCommandTests/Commands/Iris/IrisStatusTests.swift
  • Tests/DomainTests/Iris/AppBundles/AppBundleTests.swift
  • Tests/DomainTests/Iris/IrisSessionTests.swift
  • Tests/DomainTests/Iris/IrisStatusTests.swift
  • Tests/DomainTests/TestHelpers/MockRepositoryFactory.swift
  • Tests/InfrastructureTests/Iris/IrisCreateAppDocumentTests.swift
  • apps/asc-web/ARCHITECTURE.md
  • apps/asc-web/command-center/index.html
  • apps/asc-web/command-center/js/presentation/navigation.js
  • apps/asc-web/command-center/js/presentation/pages/iris.js
  • apps/asc-web/console/js/presentation/nav-data.js
  • apps/asc-web/shared/infrastructure/mock-data.js
  • docs/features/iris.md
  • homepage/es/index.html
  • homepage/fr/index.html
  • homepage/i18n/en.json
  • homepage/i18n/es.json
  • homepage/i18n/fr.json
  • homepage/i18n/it.json
  • homepage/i18n/ja.json
  • homepage/i18n/ko.json
  • homepage/i18n/pt.json
  • homepage/i18n/ru.json
  • homepage/i18n/th.json
  • homepage/i18n/vi.json
  • homepage/i18n/zh-TW.json
  • homepage/i18n/zh.json
  • homepage/index.html
  • homepage/it/index.html
  • homepage/ja/index.html
  • homepage/ko/index.html
  • homepage/pt/index.html
  • homepage/ru/index.html
  • homepage/sitemap.xml
  • homepage/template.html
  • homepage/th/index.html
  • homepage/vi/index.html
  • homepage/zh-TW/index.html
  • homepage/zh/index.html

Comment on lines +73 to +74
<td>${(app.platforms || []).map(p => `<span class="platform-badge">${p}</span>`).join(' ')}</td>
<td class="cell-mono">${app.id}</td>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Escape user-controlled values to prevent XSS.

app.platforms elements and app.id are rendered without escapeHTML(). If these values contain HTML characters, they could be exploited.

🔒 Proposed fix
-          <td>${(app.platforms || []).map(p => `<span class="platform-badge">${p}</span>`).join(' ')}</td>
-          <td class="cell-mono">${app.id}</td>
+          <td>${(app.platforms || []).map(p => `<span class="platform-badge">${escapeHTML(p)}</span>`).join(' ')}</td>
+          <td class="cell-mono">${escapeHTML(app.id)}</td>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/asc-web/command-center/js/presentation/pages/iris.js` around lines 73 -
74, The template is inserting unescaped user-controlled values: in the platforms
mapping and app.id; wrap each platform string and the app.id with the
HTML-escaping helper (e.g., escapeHTML) before injecting into the template to
prevent XSS—use escapeHTML(p) inside the map when creating the <span
class="platform-badge"> and use escapeHTML(app.id) for the cell-mono output (and
import/ensure escapeHTML is available in iris.js if not).

Comment on lines +245 to +258
// ===== Iris Status =====
irisStatus: {
data: [
{ source: 'browser', cookieCount: 7 },
],
},

// ===== Iris App Bundles =====
irisApps: {
data: [
{ id: '6449071230', name: 'PhotoSync Pro', bundleId: 'com.example.photosync', sku: 'PHOTOSYNC2024', primaryLocale: 'en-US', platforms: ['IOS'] },
{ id: '6449071231', name: 'TaskFlow', bundleId: 'com.example.taskflow', sku: 'TASKFLOW2024', primaryLocale: 'en-US', platforms: ['IOS', 'MAC_OS'] },
],
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add Iris affordances to mocks to mirror real CLI payloads.

irisStatus and irisApps are missing affordances, while real Iris responses expose next-step commands. This can drift command-center behavior in mock mode.

💡 Proposed mock-shape alignment
   irisStatus: {
     data: [
-      { source: 'browser', cookieCount: 7 },
+      {
+        source: 'browser',
+        cookieCount: 7,
+        affordances: {
+          listApps: 'asc iris apps list',
+          createApp: 'asc iris apps create --name <name> --bundle-id <id> --sku <sku>',
+        },
+      },
     ],
   },

   // ===== Iris App Bundles =====
   irisApps: {
     data: [
-      { id: '6449071230', name: 'PhotoSync Pro', bundleId: 'com.example.photosync', sku: 'PHOTOSYNC2024', primaryLocale: 'en-US', platforms: ['IOS'] },
-      { id: '6449071231', name: 'TaskFlow', bundleId: 'com.example.taskflow', sku: 'TASKFLOW2024', primaryLocale: 'en-US', platforms: ['IOS', 'MAC_OS'] },
+      {
+        id: '6449071230',
+        name: 'PhotoSync Pro',
+        bundleId: 'com.example.photosync',
+        sku: 'PHOTOSYNC2024',
+        primaryLocale: 'en-US',
+        platforms: ['IOS'],
+        affordances: {
+          listAppInfos: 'asc app-infos list --app-id 6449071230',
+          listVersions: 'asc versions list --app-id 6449071230',
+        },
+      },
+      {
+        id: '6449071231',
+        name: 'TaskFlow',
+        bundleId: 'com.example.taskflow',
+        sku: 'TASKFLOW2024',
+        primaryLocale: 'en-US',
+        platforms: ['IOS', 'MAC_OS'],
+        affordances: {
+          listAppInfos: 'asc app-infos list --app-id 6449071231',
+          listVersions: 'asc versions list --app-id 6449071231',
+        },
+      },
     ],
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/asc-web/shared/infrastructure/mock-data.js` around lines 245 - 258, The
mock objects irisStatus and irisApps lack the affordances field present in real
Iris responses; update the mock entries for irisStatus and irisApps to include
an affordances property that mirrors the real CLI payload (e.g., an array of
affordance objects with rel, href, method and optional body/schema) so
command-center in mock mode can see next-step commands; modify the irisStatus
data item and each irisApps.data item to include appropriate affordances (for
example affordances for refresh/status-check on irisStatus and for
update/fetch/details on irisApps) keeping existing keys (id, name, bundleId,
etc.) intact.

Comment on lines +10 to +17
### 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

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add missing Changed and Fixed sections under [Unreleased].

[Unreleased] currently includes only ### Added; the repo guideline requires Added, Changed, and Fixed categories.

📝 Suggested patch
 ## [Unreleased]

 ### 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
+
+### Changed
+- None.
+
+### Fixed
+- None.

As per coding guidelines CHANGELOG.md: "Add changelog entries under [Unreleased] section using Keep a Changelog format with categories: Added, Changed, Fixed".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### 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
### 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
### Changed
- None.
### Fixed
- None.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 10 - 17, The [Unreleased] section currently only
has "### Added"; add missing "### Changed" and "### Fixed" headings under the
same [Unreleased] block (adjacent to the existing "### Added") and include
either relevant entries or placeholder lines (e.g., "N/A" or "None") under each
to satisfy the repository's Keep a Changelog format; ensure the headings are
titled exactly "### Changed" and "### Fixed" to match guidelines and maintain
the existing markdown structure.

Comment on lines +39 to +43
```
Source Cookies
------ -------
browser 5
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifier to fenced code block.

The table output example is missing a language identifier. Use text or plaintext for non-code output.

Proposed fix
-```
+```text
 Source   Cookies
 ------  -------
 browser  5
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.21.0)</summary>

[warning] 39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/features/iris.md around lines 39 - 43, Update the fenced code block
showing the table beginning with "Source Cookies" to include a language
identifier such as text or plaintext after the opening backticks so the
block becomes "```text"; locate the fenced block that contains the lines "Source
Cookies", "------ -------", "browser 5" and add the identifier to the opening
fence.


</details>

<!-- fingerprinting:phantom:poseidon:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +156 to +176
```
┌─────────────────────────────────────────────────────────────┐
│ ASCCommand │
│ ┌──────────┐ ┌────────────┐ ┌────────────────┐ │
│ │IrisStatus │ │IrisAppsList│ │IrisAppsCreate │ │
│ └─────┬────┘ └─────┬──────┘ └───────┬────────┘ │
│ │ │ │ │
│ └─────────────┴──────────────────┘ │
│ │ ClientProvider │
├──────────────────────┼──────────────────────────────────────┤
│ Infrastructure │ │
│ ┌───────────────────┴──────────┐ ┌──────────────────────┐ │
│ │ BrowserIrisCookieProvider │ │ IrisSDKAppBundleRepo │ │
│ │ (SweetCookieKit) │ │ (IrisClient → HTTP) │ │
│ └──────────────────────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Domain │
│ IrisSession · IrisStatus · IrisCookieProvider(@Mockable) │
│ AppBundle · IrisAppBundleRepository(@Mockable) │
└─────────────────────────────────────────────────────────────┘
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifier to architecture diagram code block.

Use text for ASCII diagrams to satisfy linting and improve rendering consistency.

Proposed fix
-```
+```text
 ┌─────────────────────────────────────────────────────────────┐
 │  ASCCommand                                                 │
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 156-156: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/features/iris.md` around lines 156 - 176, Add the language identifier
"text" to the fenced code block containing the ASCII architecture diagram so the
diagram is marked as text (change the opening triple-backtick to "```text");
locate the ASCII diagram block (the diagram starting with
"┌─────────────────────────────────────────────────────────────┐" and the
triple-backtick fence) and update only the opening fence to include text to
satisfy linting and improve rendering.

Comment on lines +22 to +30
{
"identity" : "sweetcookiekit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/steipete/SweetCookieKit.git",
"state" : {
"revision" : "4d5b71ffbb296937dc5ee8472f64721bca771cf0",
"version" : "0.4.0"
}
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

What is the latest release of SweetCookieKit (github.com/steipete/SweetCookieKit), and are there any published security advisories affecting version 0.4.0?

💡 Result:

The latest release of SweetCookieKit (github.com/steipete/SweetCookieKit) is version 0.3.0, released on 2026-01-20. There are no published security advisories for the project, including for version 0.4.0 (which does not appear to exist). The GitHub security overview states there are no published advisories, and the repo has no SECURITY.md file.

Citations:


Update SweetCookieKit pin to version 0.3.0; version 0.4.0 does not exist.

The pinned version 0.4.0 does not exist—the latest release is 0.3.0 (2026-01-20). Update the pin accordingly to resolve dependency resolution failures. No known security advisories affect this dependency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Package.resolved` around lines 22 - 30, The Package.resolved entry for the
dependency with "identity" : "sweetcookiekit" is pinned to a non-existent
version "0.4.0"; update that entry to the real release "0.3.0" by changing the
"version" value to "0.3.0" and also update the "revision" field to the commit
hash that corresponds to the SweetCookieKit v0.3.0 tag (or run swift package
resolve / regenerate Package.resolved to automatically populate the correct
revision). Locate the object with identity "sweetcookiekit" and apply these
changes so dependency resolution succeeds.

Comment on lines +6 to +7
public struct IrisSession: Sendable, Equatable {
public let cookies: String
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add required Domain-model conformances before merge.

Line 6 defines IrisSession without Codable, and the model also lacks AffordanceProviding + affordances, which breaks the Domain model contract used across this repo.

As per coding guidelines: "All Domain layer models are public struct + Sendable + Equatable + Codable" and "All domain models must implement AffordanceProviding protocol with an affordances field containing ready-to-run CLI commands".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Domain/Iris/IrisSession.swift` around lines 6 - 7, IrisSession
currently only conforms to Sendable and Equatable; update the public struct
IrisSession to also conform to Codable and AffordanceProviding, add a public let
affordances: [Affordance] property (or the concrete affordance type used in the
repo) and implement the required AffordanceProviding requirements so the model
exposes ready-to-run CLI commands; ensure the struct signature (IrisSession) and
its initializer remain stable and that the affordances field is populated with
the appropriate command definitions expected by the Domain (use existing
Affordance/CLI helpers elsewhere as a reference).

Comment on lines +18 to +22
public func resolveStatus() throws -> IrisStatus {
let (cookies, source) = try resolve()
let count = cookies.components(separatedBy: "; ").count
return IrisStatus(source: source, cookieCount: count)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Cookie count may be inaccurate for environment variable input.

cookieCount is calculated by splitting on "; ", but the environment variable format isn't validated. If users provide cookies with different separators (e.g., ";" without space), the count will be incorrect.

💡 Suggested improvement
     public func resolveStatus() throws -> IrisStatus {
         let (cookies, source) = try resolve()
-        let count = cookies.components(separatedBy: "; ").count
+        // Handle both "; " and ";" separators for robustness
+        let count = cookies.split { $0 == ";" }.count
         return IrisStatus(source: source, cookieCount: count)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Infrastructure/Iris/BrowserIrisCookieProvider.swift` around lines 18
- 22, The cookie count in resolveStatus() is computed with
cookies.components(separatedBy: "; "), which miscounts when separators lack a
space; update resolveStatus() to robustly parse the raw cookies returned by
resolve() by splitting on ";" (not "; "), trimming whitespace from each segment
with trimmingCharacters(in: .whitespacesAndNewlines), filtering out empty
segments, and using the resulting array's count when constructing
IrisStatus(cookieCount:); reference resolveStatus(), resolve(), and IrisStatus
in your change.

Comment on lines +13 to +16
@Test func `status from environment shows source`() {
let status = IrisStatus(source: .environment, cookieCount: 3)
#expect(status.source == .environment)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Also assert cookieCount for the environment-source path.

The environment case currently validates only source; adding cookieCount keeps parity with the browser case and closes a small regression gap.

✅ Suggested test assertion
     `@Test` func `status from environment shows source`() {
         let status = IrisStatus(source: .environment, cookieCount: 3)
         `#expect`(status.source == .environment)
+        `#expect`(status.cookieCount == 3)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/DomainTests/Iris/IrisStatusTests.swift` around lines 13 - 16, The test
`status from environment shows source` creates IrisStatus(source: .environment,
cookieCount: 3) but only asserts `status.source`; add an assertion to also
verify `status.cookieCount == 3` in that test (same pattern used in the browser
case) so both source and cookieCount are validated for the environment path.

@codecov
Copy link

codecov bot commented Mar 21, 2026

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@hanrw hanrw changed the title Iris Create apps directly from the terminal #7 Mar 21, 2026
@hanrw hanrw changed the title Create apps directly from the terminal #7 Create apps directly from the terminal Mar 21, 2026
@hanrw
Copy link
Member Author

hanrw commented Mar 21, 2026

Create apps directly from the terminal #7

hanrw added 3 commits March 21, 2026 13:05
- Introduce feature.yml with structured prompts for proposing enhancements
- Add bug.yml to standardize bug report submission and reproduction steps
ci(workflow): exclude Iris modules from coverage

ci(codecov): add Iris files to ignore list in coverage configuration
…e decoding

- Validate IrisAPIError descriptions for various cases
- Verify IrisCookieError descriptive messages
- Cover decoding of app bundle list, single bundle, and nil attributes
- Test mapping from API resources to AppBundle model with fallbacks
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
Sources/Infrastructure/Iris/AppBundles/IrisSDKAppBundleRepository.swift (1)

15-18: ⚠️ Potential issue | 🟠 Major

Pagination is still missing for app bundle listing.

Using a fixed limit=300 can silently truncate results for larger accounts. Please paginate (or at minimum surface a clear truncation warning) before release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Infrastructure/Iris/AppBundles/IrisSDKAppBundleRepository.swift`
around lines 15 - 18, The app bundle list currently uses a fixed
URLQueryItem(limit= "300") in IrisSDKAppBundleRepository which can truncate
results; change the listing logic in IrisSDKAppBundleRepository (the method
building those URLQueryItem entries and parsing the response) to perform proper
pagination instead of a single fixed-limit request: detect the API's pagination
mechanism (offset/page or cursor/next token) from the response, loop fetching
subsequent pages (appending URLQueryItem like "page"/"offset" or
"cursor"/"next") until no more results, and merge results into the single
returned collection; if the backend cannot provide pagination, replace the fixed
limit with a warning/log that results may be truncated and surface the fact to
callers (e.g., via an error/flag) rather than silently returning only 300 items.
🧹 Nitpick comments (2)
Tests/InfrastructureTests/Iris/IrisResponseDecodingTests.swift (2)

90-135: Prefer behavior-level tests over direct mapper internals.

These cases are centered on mapToAppBundle internals rather than user-observable repository outcomes. Consider asserting through listAppBundles/createApp behavior with fixtures so refactors of internal mapping don’t force unnecessary test churn.

As per coding guidelines, "Test cases must reflect the user's mental model — describe what the user sees and expects, not internal implementation details".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/InfrastructureTests/Iris/IrisResponseDecodingTests.swift` around lines
90 - 135, Replace the fragile, implementation-focused tests that call
IrisSDKAppBundleRepository.mapToAppBundle directly with behavior-level tests
that exercise repository API surface (e.g., call
IrisSDKAppBundleRepository.listAppBundles or createApp using test fixtures) and
assert the user-observable results (returned AppBundle fields) instead; update
tests that currently reference mapToAppBundle, makeResource, and direct field
assertions to create fixtures covering nil name/platforms/primaryLocale
scenarios, invoke the repository method a consumer would use, and assert the
resulting AppBundle properties so refactors to the mapper implementation won’t
break the tests.

1-7: Align this AppBundles suite path with the Domain hierarchy.

Since this suite validates AppBundles decoding/mapping, consider placing it under an Iris/AppBundles mirrored test path for consistency with the required structure.

As per coding guidelines, "Infrastructure and test folder structures must mirror the Domain layer structure exactly".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/InfrastructureTests/Iris/IrisResponseDecodingTests.swift` around lines
1 - 7, This test suite (IrisResponseDecodingTests) must be relocated into the
test hierarchy that mirrors the Domain layer's Iris/AppBundles structure and
renamed/organized to reflect that it's validating AppBundles decoding; move the
test into the Iris -> AppBundles test group and rename the struct (for example
to AppBundlesResponseDecodingTests or IrisAppBundlesResponseDecodingTests) and
update any references/imports so the suite and file path match the Domain
Iris/AppBundles namespace exactly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Sources/Infrastructure/Iris/AppBundles/IrisSDKAppBundleRepository.swift`:
- Around line 15-18: The app bundle list currently uses a fixed
URLQueryItem(limit= "300") in IrisSDKAppBundleRepository which can truncate
results; change the listing logic in IrisSDKAppBundleRepository (the method
building those URLQueryItem entries and parsing the response) to perform proper
pagination instead of a single fixed-limit request: detect the API's pagination
mechanism (offset/page or cursor/next token) from the response, loop fetching
subsequent pages (appending URLQueryItem like "page"/"offset" or
"cursor"/"next") until no more results, and merge results into the single
returned collection; if the backend cannot provide pagination, replace the fixed
limit with a warning/log that results may be truncated and surface the fact to
callers (e.g., via an error/flag) rather than silently returning only 300 items.

---

Nitpick comments:
In `@Tests/InfrastructureTests/Iris/IrisResponseDecodingTests.swift`:
- Around line 90-135: Replace the fragile, implementation-focused tests that
call IrisSDKAppBundleRepository.mapToAppBundle directly with behavior-level
tests that exercise repository API surface (e.g., call
IrisSDKAppBundleRepository.listAppBundles or createApp using test fixtures) and
assert the user-observable results (returned AppBundle fields) instead; update
tests that currently reference mapToAppBundle, makeResource, and direct field
assertions to create fixtures covering nil name/platforms/primaryLocale
scenarios, invoke the repository method a consumer would use, and assert the
resulting AppBundle properties so refactors to the mapper implementation won’t
break the tests.
- Around line 1-7: This test suite (IrisResponseDecodingTests) must be relocated
into the test hierarchy that mirrors the Domain layer's Iris/AppBundles
structure and renamed/organized to reflect that it's validating AppBundles
decoding; move the test into the Iris -> AppBundles test group and rename the
struct (for example to AppBundlesResponseDecodingTests or
IrisAppBundlesResponseDecodingTests) and update any references/imports so the
suite and file path match the Domain Iris/AppBundles namespace exactly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb6b2e75-8ec8-40d2-8c0f-384248e28293

📥 Commits

Reviewing files that changed from the base of the PR and between 05581d8 and 6ab783c.

📒 Files selected for processing (8)
  • .github/ISSUE_TEMPLATE/bug.yml
  • .github/ISSUE_TEMPLATE/feature.yml
  • .github/workflows/ci.yml
  • Sources/Infrastructure/Iris/AppBundles/IrisSDKAppBundleRepository.swift
  • Tests/InfrastructureTests/Iris/IrisAPIErrorTests.swift
  • Tests/InfrastructureTests/Iris/IrisCookieErrorTests.swift
  • Tests/InfrastructureTests/Iris/IrisResponseDecodingTests.swift
  • codecov.yml
✅ Files skipped from review due to trivial changes (4)
  • codecov.yml
  • .github/ISSUE_TEMPLATE/bug.yml
  • .github/ISSUE_TEMPLATE/feature.yml
  • .github/workflows/ci.yml

@hanrw hanrw merged commit 5e6a865 into main Mar 21, 2026
4 checks passed
@hanrw hanrw mentioned this pull request Mar 21, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant