Repository: mas-cli/mas Branch: main Commit: 535562b304eb Files: 138 Total size: 454.7 KB Directory structure: gitextract_u6q5_k68/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── 01-bug-report.yaml │ │ └── 02-feature-request.yaml │ ├── actionlint.yaml │ ├── dependabot.yaml │ ├── release.yaml │ └── workflows/ │ ├── build-test.yaml │ ├── codeql.yaml │ ├── release-published.yaml │ └── tag-pushed.yaml ├── .gitignore ├── .markdownlint-cli2.yaml ├── .periphery.yaml ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── .xcode-version ├── .yamllint.yaml ├── Brewfile ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Documentation/ │ ├── Sample.swift │ └── style.md ├── LICENSE ├── Package.resolved ├── Package.swift ├── Plugins/ │ └── MASBuildToolPlugin/ │ └── MASBuildToolPlugin.swift ├── README.md ├── Scripts/ │ ├── _setup_script │ ├── bootstrap │ ├── build │ ├── clean │ ├── format │ ├── generate_manual │ ├── generate_token │ ├── lint │ ├── package │ ├── prebuild │ ├── release_cancel │ ├── release_start │ ├── setup_workflow_repo │ ├── test │ ├── update_dependencies │ ├── update_headers │ └── version ├── Sources/ │ ├── PrivateFrameworks/ │ │ ├── PrivateFrameworks.c │ │ └── include/ │ │ ├── CommerceKit/ │ │ │ ├── CKDownloadDirectory.h │ │ │ ├── CKDownloadQueue.h │ │ │ ├── CKDownloadQueueObserver-Protocol.h │ │ │ ├── CKPurchaseController.h │ │ │ ├── CKServiceInterface.h │ │ │ ├── CommerceKit.h │ │ │ └── module.modulemap │ │ └── StoreFoundation/ │ │ ├── ISAccountService-Protocol.h │ │ ├── ISServiceProxy.h │ │ ├── ISStoreAccount.h │ │ ├── SSDownload.h │ │ ├── SSDownloadMetadata.h │ │ ├── SSDownloadPhase.h │ │ ├── SSDownloadStatus.h │ │ ├── SSPurchase.h │ │ ├── SSPurchaseResponse.h │ │ ├── StoreFoundation.h │ │ └── module.modulemap │ └── mas/ │ ├── AppStore/ │ │ ├── AppStoreAction+download.swift │ │ ├── AppStoreAction.swift │ │ └── Region.swift │ ├── Commands/ │ │ ├── Config.swift │ │ ├── Get.swift │ │ ├── Home.swift │ │ ├── Install.swift │ │ ├── List.swift │ │ ├── Lookup.swift │ │ ├── Lucky.swift │ │ ├── MAS.swift │ │ ├── Open.swift │ │ ├── OptionGroups/ │ │ │ ├── CatalogAppIDsOptionGroup.swift │ │ │ ├── ForceBundleIDOptionGroup.swift │ │ │ ├── ForceOptionGroup.swift │ │ │ ├── InstalledAppIDsOptionGroup.swift │ │ │ ├── OutdatedAccuracy.swift │ │ │ ├── OutdatedAppOptionGroup.swift │ │ │ ├── SearchTermOptionGroup.swift │ │ │ └── VerboseOptionGroup.swift │ │ ├── Outdated.swift │ │ ├── Reset.swift │ │ ├── Search.swift │ │ ├── Seller.swift │ │ ├── SignOut.swift │ │ ├── Uninstall.swift │ │ ├── Update.swift │ │ └── Version.swift │ ├── Controllers/ │ │ ├── CatalogApp+ITunesSearch.swift │ │ └── InstalledApp+Spotlight.swift │ ├── Errors/ │ │ └── MASError.swift │ ├── Models/ │ │ ├── AppID.swift │ │ ├── CatalogApp.swift │ │ ├── CatalogAppResults.swift │ │ ├── InstalledApp.swift │ │ └── OutdatedApp.swift │ ├── Network/ │ │ └── URL.swift │ └── Utilities/ │ ├── Collection.swift │ ├── FileHandle.swift │ ├── Group.swift │ ├── KeyPath.swift │ ├── Optional.swift │ ├── Pipe.swift │ ├── Printer.swift │ ├── Process.swift │ ├── ProcessInfo.swift │ ├── RangeReplaceableCollection.swift │ ├── Sequence.swift │ ├── String.swift │ ├── Sudo.swift │ ├── User.swift │ ├── UserAndGroup.swift │ └── Version+SemVer.swift ├── Tests/ │ └── MASTests/ │ ├── Commands/ │ │ ├── MASTests+Home.swift │ │ ├── MASTests+List.swift │ │ ├── MASTests+Lookup.swift │ │ ├── MASTests+Search.swift │ │ ├── MASTests+Seller.swift │ │ └── MASTests+Version.swift │ ├── Controllers/ │ │ └── MASTests+CatalogApp+ITunesSearch.swift │ ├── Extensions/ │ │ └── Data.swift │ ├── MASTests.swift │ ├── Models/ │ │ ├── MASTests+CatalogApp.swift │ │ └── MASTests+CatalogAppResults.swift │ ├── Resources/ │ │ ├── bbedit.json │ │ ├── slack-lookup.json │ │ ├── slack.json │ │ ├── things-lookup.json │ │ └── things.json │ └── Utilities/ │ └── Consequences.swift └── contrib/ └── completion/ ├── mas-completion.bash └── mas.fish ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # # .editorconfig # mas # # EditorConfig 0.17.2 # root = true [*] charset = utf-8 continuation_indent_size = 0 end_of_line = lf indent_size = tab indent_style = tab insert_final_newline = true max_line_length = 120 quote_type = single spelling_language = en_US tab_width = 2 trim_trailing_whitespace = true [*.md] # Trailing spaces have meaning in Markdown max_line_length = 80 trim_trailing_whitespace = false [{*.yaml,*.yml}] max_line_length = 80 [Scripts/*] max_line_length = 80 ================================================ FILE: .gitattributes ================================================ # Do not remove potentially intentional trailing spaces in Markdown. **/*.md whitespace=-blank-at-eol ================================================ FILE: .github/CODEOWNERS ================================================ # # .github/CODEOWNERS # /.github/ @mas-cli/admins ================================================ FILE: .github/ISSUE_TEMPLATE/01-bug-report.yaml ================================================ --- name: Bug Report description: Report a bug. labels: [\U0001F41B bug] body: - type: textarea id: config attributes: label: Configuration description: Output of `mas config` placeholder: Output of `mas config` render: text validations: required: true - type: textarea id: description attributes: label: Bug description placeholder: | Bug description Include expected & actual output, plus other pertinent info Instead of screenshots, prefer pasting copied commands & output into console blocks, formatted as instructed below validations: required: true - type: textarea id: reproduction attributes: label: Steps to reproduce placeholder: | Steps to reproduce Instead of screenshots, prefer pasting copied commands & output into console blocks, formatted as instructed below validations: required: true - type: markdown attributes: value: | ## Console command & output formatting instructions Provide console commands & output as copied, pasted & formatted text, instead of as screenshots. If long descriptive text or screenshots of dialogs or apps are necessary, provide them between console blocks. Format commands & output as follows (where `…` is a placeholder): - Use a console block: start with ```` ```console ````, end with ```` ``` ````, each on its own line - Prefix each non-console step (or comment) with two hashes & a space: `## …` - Remove shell prompts; instead, prefix each console command with a dollar sign & a space: `$ …` - Prefix each output line beginning with `#`, `$`, `%`, or `>` with an additional instance of that character: `##…`, `$$…`, `%%…`, or `>>…` - Write all other output lines without any prefix: `…` e.g.: ````text ```console ## In the App Store GUI, click on… $ mas list 123 App 1 (4.5.6) 124 App 2 (10.2) $ mas outdated 123 App 1 (4.5.6 -> 4.5.7) ``` ```` ================================================ FILE: .github/ISSUE_TEMPLATE/02-feature-request.yaml ================================================ --- name: Feature Request description: Request a feature. labels: [\U0001F195 feature request] body: - type: textarea id: problems attributes: label: Problem(s) addressed placeholder: | Problem(s) addressed Instead of screenshots, prefer pasting copied commands & output into console blocks, formatted as instructed below validations: required: true - type: textarea id: proposals attributes: label: Proposed solution(s) placeholder: | Proposed solution(s) Instead of screenshots, prefer pasting copied commands & output into console blocks, formatted as instructed below validations: required: true - type: markdown attributes: value: | ## Console command & output formatting instructions Provide console commands & output as copied, pasted & formatted text, instead of as screenshots. If long descriptive text or screenshots of dialogs or apps are necessary, provide them between console blocks. Format commands & output as follows (where `…` is a placeholder): - Use a console block: start with ```` ```console ````, end with ```` ``` ````, each on its own line - Prefix each non-console step (or comment) with two hashes & a space: `## …` - Remove shell prompts; instead, prefix each console command with a dollar sign & a space: `$ …` - Prefix each output line beginning with `#`, `$`, `%`, or `>` with an additional instance of that character: `##…`, `$$…`, `%%…`, or `>>…` - Write all other output lines without any prefix: `…` e.g.: ````text ```console ## In the App Store GUI, click on… $ mas list 123 App 1 (4.5.6) 124 App 2 (10.2) $ mas outdated 123 App 1 (4.5.6 -> 4.5.7) ``` ```` ================================================ FILE: .github/actionlint.yaml ================================================ # # .github/actionlint.yaml # mas # # actionlint 1.7.11 # --- self-hosted-runner: labels: [macos-26-intel] ================================================ FILE: .github/dependabot.yaml ================================================ --- version: 2 updates: - package-ecosystem: github-actions schedule: interval: daily directory: / labels: [📚 dependencies] commit-message: prefix: ⬆️ include: scope - package-ecosystem: swift schedule: interval: daily directory: / labels: [📚 dependencies] commit-message: prefix: ⬆️ include: scope ================================================ FILE: .github/release.yaml ================================================ --- changelog: categories: - title: 🚀 Features labels: [🆕 feature request] - title: 🐛 Bug Fixes labels: [🐛 bug] - title: Changes labels: ['*'] ================================================ FILE: .github/workflows/build-test.yaml ================================================ # # .github/workflows/build-test.yaml # --- name: Build, Test, and Lint on: pull_request: branches: [main] push: branches: [main] concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: true permissions: {} jobs: build-test: name: Build, Test, and Lint strategy: matrix: include: - runner: macos-14 xcode: brew - runner: macos-15 - runner: macos-15-intel - runner: macos-26 - runner: macos-26-intel runs-on: ${{matrix.runner}} defaults: run: # Force all run commands to not use Rosetta 2 on arm64 shell: ${{endsWith(matrix.runner, '-intel') && '/bin/zsh -Negku {0}' || 'arch -arm64 /bin/zsh -Negku {0}'}} steps: - name: 🛒 Checkout repo env: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} uses: actions/checkout@v6 with: # Include all history & tags for Scripts/version fetch-depth: 0 - name: 🔧 Setup repo run: Scripts/setup_workflow_repo - name: 🛠 Select Xcode version if: matrix.xcode != 'brew' run: xcodes select ${{matrix.xcode}} - name: 👢 Bootstrap run: Scripts/bootstrap - name: 🕊 Use Homebrew Core Swift if: matrix.xcode == 'brew' run: | brew install swift printf $'%s\n' "$(brew --prefix swift)/bin" >> "${GITHUB_PATH}" - name: 🏗 Build run: Scripts/build build-test -c release - name: 🧪 Test if: matrix.xcode != 'brew' run: Scripts/test - name: 🚨 Lint if: matrix.xcode != 'brew' run: Scripts/lint ================================================ FILE: .github/workflows/codeql.yaml ================================================ # # .github/workflows/codeql.yaml # --- name: CodeQL on: push: branches: [main] pull_request: branches: [main] schedule: - cron: 44 14 * * 4 workflow_dispatch: {} jobs: analyze: name: Analyze ${{matrix.language}} runs-on: macos-26 permissions: security-events: write strategy: fail-fast: false matrix: include: - language: actions build-mode: none - language: swift build-mode: manual steps: - name: 🛒 Checkout repo env: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} uses: actions/checkout@v6 with: # Include all history & tags for Scripts/version fetch-depth: 0 - name: 🔧 Setup repo run: Scripts/setup_workflow_repo - name: 🔩 Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{matrix.language}} build-mode: ${{matrix.build-mode}} queries: ${{matrix.language == 'swift' && '+security-and-quality' || ''}} - name: 🏗 Build Swift if: matrix.language == 'swift' shell: bash run: | xcodes select Scripts/build codeql -c release - name: 🔍 Perform CodeQL analysis uses: github/codeql-action/analyze@v4 with: category: /language:${{matrix.language}} ================================================ FILE: .github/workflows/release-published.yaml ================================================ # # .github/workflows/release-published.yaml # --- name: release-published on: release: types: [published] permissions: actions: read contents: write pull-requests: write defaults: run: # Force all run commands to not use Rosetta 2 shell: arch -arm64 /bin/zsh -Negku {0} jobs: release-published: if: ${{!github.event.repository.fork}} runs-on: macos-26 steps: - name: 🛒 Checkout repo env: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} uses: actions/checkout@v6 with: # Include all history & tags for Scripts/version fetch-depth: 0 - name: 🔧 Setup repo run: Scripts/setup_workflow_repo - name: 🚰 Apply pr-pull label to tap formula bump PR env: TOKEN_APP_ID: ${{secrets.TOKEN_APP_ID}} TOKEN_APP_INSTALLATION_ID: ${{secrets.TOKEN_APP_INSTALLATION_ID}} TOKEN_APP_PRIVATE_KEY: ${{secrets.TOKEN_APP_PRIVATE_KEY}} run: | export GH_TOKEN="$(Scripts/generate_token)" unsetopt errexit bump_url="$(gh release -R "${GITHUB_REPOSITORY}" download "${GITHUB_REF_NAME}" -p bump.url -O - 2>/dev/null)" found_bump_url="${?}" setopt errexit if [[ "${found_bump_url}" -eq 0 ]]; then [[ -n "${bump_url}" ]] && gh pr edit "${bump_url}" --add-label pr-pull gh release -R "${GITHUB_REPOSITORY}" delete-asset "${GITHUB_REF_NAME}" bump.url -y else printf $'No tap formula bump PR URL found for tag %s\n' "${GITHUB_REF_NAME}" fi ================================================ FILE: .github/workflows/tag-pushed.yaml ================================================ # # .github/workflows/tag-pushed.yaml # --- name: tag-pushed on: push: tags: ['**'] permissions: contents: write defaults: run: # Force all run commands to not use Rosetta 2 shell: arch -arm64 /bin/zsh -Negku {0} jobs: tag-pushed: if: ${{!github.event.repository.fork}} runs-on: macos-26 steps: - name: 🛒 Checkout repo env: GIT_CONFIG_COUNT: 1 GIT_CONFIG_KEY_0: init.defaultBranch GIT_CONFIG_VALUE_0: ${{github.event.repository.default_branch}} uses: actions/checkout@v6 with: # Include all history & tags for Scripts/version fetch-depth: 0 - name: 🔧 Setup repo run: Scripts/setup_workflow_repo - name: 🖋 Delete tag lacking valid signature run: | git fetch --force origin "${GITHUB_REF}:${GITHUB_REF}" if [[\ "$(git cat-file tag "${GITHUB_REF_NAME}")" != *'-----BEGIN SSH SIGNATURE-----'*'-----END SSH SIGNATURE-----'\ ]]; then printf $'Error: Deleting tag %s because it does not have a valid signature\n' "${GITHUB_REF_NAME}" >&2 git push -d origin "${GITHUB_REF_NAME}" exit 1 fi - name: 🏷 Exit if not a version tag run: | if [[ ! "${GITHUB_REF_NAME}" =~ '^v[[:digit:]]+(\.[[:digit:]]+)*(-(alpha|beta|rc)\.[[:digit:]]+)?$' ]]; then printf $'Exiting because %s is not a version tag\n' "${GITHUB_REF_NAME}" exit 2 fi - name: 🌳 Delete version tag not on default branch env: DEFAULT_BRANCH_NAME: ${{github.event.repository.default_branch}} run: | git fetch --force origin "${DEFAULT_BRANCH_NAME}:${DEFAULT_BRANCH_NAME}" if ! git merge-base --is-ancestor "${GITHUB_REF_NAME}" "${DEFAULT_BRANCH_NAME}"; then printf $'Error: Deleting version tag %s because it is not on the %s branch\n' "${GITHUB_REF_NAME}"\ "${DEFAULT_BRANCH_NAME}" >&2 git push -d origin "${GITHUB_REF_NAME}" exit 3 fi - name: 🛠 Select Xcode version run: xcodes select - name: 📦 Build Apple & Intel installers run: | Scripts/package package --arch arm64 Scripts/package package --arch x86_64 - name: 🚰 Bump tap formula env: TOKEN_APP_ID: ${{secrets.TOKEN_APP_ID}} TOKEN_APP_INSTALLATION_ID: ${{secrets.TOKEN_APP_INSTALLATION_ID}} TOKEN_APP_PRIVATE_KEY: ${{secrets.TOKEN_APP_PRIVATE_KEY}} run: | export HOMEBREW_GITHUB_API_TOKEN="$(Scripts/generate_token)" brew tap "${GITHUB_REPOSITORY_OWNER}/tap" unsetopt errexit bump_output="$(brew bump-formula-pr\ --tag "${GITHUB_REF_NAME}"\ --revision "${GITHUB_SHA}"\ --no-fork\ --no-browse\ --online\ --strict\ --verbose\ "${GITHUB_REPOSITORY_OWNER}/tap/mas"\ 2>&1)" exit_status="${?}" setopt errexit printf %s "${bump_output}" printf %s "${${(f)bump_output}[-1]}" > .build/bump.url exit "${exit_status}" - name: 📝 Create draft release env: GH_TOKEN: ${{github.token}} run: | gh release create\ "${GITHUB_REF_NAME}"\ ".build/mas-${GITHUB_REF_NAME#v}-arm64.pkg"\ ".build/mas-${GITHUB_REF_NAME#v}-x86_64.pkg"\ .build/bump.url\ -d\ ${"${GITHUB_REF_NAME//[^-]}":+-p}\ -t "${GITHUB_REF_NAME}: ${$(git tag -l "${GITHUB_REF_NAME}" --format='%(contents)')%%$'\n'*}"\ --generate-notes ================================================ FILE: .gitignore ================================================ /.build/ /.idea/ /.swiftpm/ /.vscode/ .DS_Store *~ ================================================ FILE: .markdownlint-cli2.yaml ================================================ # yamllint disable-line rule:line-length # yaml-language-server: $schema=https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/main/schema/markdownlint-cli2-config-schema.json # # .markdownlint-cli2.yaml # mas # # markdownlint-cli2 0.21.0 / markdownlint 0.40.0 # --- gitignore: true noBanner: true noProgress: true config: blanks-around-fences: list_items: false code-block-style: style: fenced code-fence-style: style: backtick emphasis-style: style: underscore fenced-code-language: allowed_languages: [console, shell] language_only: true heading-style: style: atx hr-style: style: --- line-length: stern: true link-image-style: shortcut: false url_inline: false no-hard-tabs: code_blocks: false spaces_per_tab: 2 no-inline-html: allowed_elements: [details, h1, summary] no-trailing-spaces: code_blocks: true strict: true ol-prefix: style: ordered proper-names: names: [mas] reference-links-images: shortcut_syntax: true strong-style: style: asterisk table-column-style: style: aligned table-pipe-style: style: leading_and_trailing ul-style: style: dash ================================================ FILE: .periphery.yaml ================================================ # # .periphery.yaml # mas # # Periphery 3.6.0 # --- color: always disable_update_check: true quiet: true relative_results: true strict: true superfluous_ignore_comments: false ================================================ FILE: .swift-version ================================================ 6.2 ================================================ FILE: .swiftformat ================================================ # # .swiftformat # mas # # SwiftFormat 0.60.1 # # Disabled rules (enabled by default) --disable hoistAwait --disable hoistTry # Disabled rules (disabled by default) #--enable blankLineAfterSwitchCase #--enable markTypes #--enable preferExplicitFalse #--enable testSuiteAccessControl # Enabled rules (disabled by default) --enable acronyms --enable blankLinesAfterGuardStatements --enable blockComments --enable isEmpty --enable noExplicitOwnership --enable noGuardInTests --enable organizeDeclarations --enable preferFinalClasses --enable preferSwiftTesting --enable privateStateVariables --enable propertyTypes --enable singlePropertyPerLine --enable sortSwitchCases --enable unusedPrivateDeclarations --enable urlMacro --enable validateTestCases --enable wrapConditionalBodies --enable wrapEnumCases --enable wrapMultilineConditionalAssignment --enable wrapMultilineFunctionChains --enable wrapSwitchCases # Rule options --acronyms ADAM,ADI,AE,ANSI,API,CD,CEO,CF,CI,CK,CPU,DAV,DOS,DS,DSID,DVD,EOF,FAQ,FAT,FD,FTP,GB,GID,GUI,GUID,HTML,HTTP,HTTPS,ID,IFS,ISO,JSON,KB,MAS,MD,MDM,MDS,MIB,MIT,NS,OS,OSX,PDF,PR,QL,SB,SDK,SHA,SS,SSH,STDQ,STDRDL,UDF,UI,UID,URL,US,UTF,UUID,VPP,XML,XPC,YAML --allow-partial-wrapping false --complex-attributes prev-line --computed-var-attributes prev-line --conditional-assignment always --exponent-grouping enabled --file-macro "#fileID" --fraction-grouping enabled --func-attributes prev-line --header //\n// {file}\n// mas\n//\n// Copyright © {created.year} mas-cli\. All rights reserved\.\n// --hex-literal-case lowercase --ifdef no-indent --import-grouping alpha --indent tab --indent-strings true --line-after-marks false --mark-categories false --max-width 120 --organization-mode type --organize-types actor,class,enum,extension,struct --property-types inferred --ranges no-space --redundant-async always --redundant-throws always --semicolons never --short-optionals always --single-line-for-each convert --stored-var-attributes prev-line --tab-width 2 --timezone utc --type-attributes prev-line --type-body-marks remove --wrap-arguments before-first --wrap-collections before-first --wrap-conditions before-first --wrap-effects never --wrap-parameters before-first --wrap-return-type never --wrap-ternary before-operators --wrap-type-aliases before-first ================================================ FILE: .swiftlint.yml ================================================ # # .swiftlint.yml # mas # # SwiftLint 0.63.2 # --- excluded: - .build/ - .idea/ - .swiftpm/ - .vscode/ opt_in_rules: - all analyzer_rules: - all disabled_rules: - closure_body_length - contrasted_opening_brace - cyclomatic_complexity - explicit_acl - explicit_enum_raw_value - explicit_self - explicit_top_level_acl - explicit_type_interface - file_header - function_body_length - large_tuple - multiple_closures_with_trailing_closure - no_extension_access_modifier - no_grouping_extension - no_magic_numbers - prefixed_toplevel_constant - strict_fileprivate - type_body_length attributes: always_on_line_above: ['@Flag', '@MainActor', '@OptionGroup'] deployment_target: macOS_deployment_target: 13 macOSApplicationExtension_deployment_target: 13 iOS_deployment_target: 99 iOSApplicationExtension_deployment_target: 99 tvOS_deployment_target: 99 tvOSApplicationExtension_deployment_target: 99 watchOS_deployment_target: 99 watchOSApplicationExtension_deployment_target: 99 file_length: ignore_comment_only_lines: true warning: 500 file_name: excluded: [Group.swift, InstalledApp+Spotlight.swift, Process.swift, User.swift] file_types_order: order: - main_type - supporting_type - extension - preview_provider - library_content_provider function_parameter_count: warning: 6 indentation_width: indentation_width: 2 include_multiline_strings: false line_length: ignores_multiline_strings: true ignores_regex_literals: true modifier_order: preferred_modifier_order: - acl - setterACL - override - isolation - dynamic - mutators - lazy - final - required - convenience - typeMethods - owned multiline_arguments: first_argument_location: next_line non_optional_string_data_conversion: include_variables: true number_separator: minimum_length: 6 opening_brace: ignore_multiline_statement_conditions: true operator_usage_whitespace: skip_aligned_constants: false prefer_key_path: restrict_to_standard_functions: false private_over_fileprivate: validate_extensions: true redundant_self: only_in_closures: false redundant_type_annotation: consider_default_literal_types_redundant: true trailing_comma: mandatory_comma: true trailing_whitespace: ignores_comments: false type_contents_order: order: - case - associated_type - type_alias - subtype - type_property - instance_property - ib_inspectable - ib_outlet - initializer - deinitializer - type_method - view_life_cycle_method - ib_action - other_method - subscript unneeded_override: affect_initializers: true unused_import: require_explicit_imports: true allowed_transitive_imports: - module: Darwin allowed_transitive_imports: - _DarwinFoundation1 - _DarwinFoundation2 - _DarwinFoundation3 - module: Swift allowed_transitive_imports: - _Concurrency - _StringProcessing vertical_whitespace_between_cases: separation: never ================================================ FILE: .xcode-version ================================================ 26.3 ================================================ FILE: .yamllint.yaml ================================================ # # .yamllint.yaml # mas # # yamllint 1.38.0 # --- extends: default locale: en_US.UTF-8 ignore-from-file: .gitignore rules: anchors: forbid-duplicated-anchors: true forbid-undeclared-aliases: true forbid-unused-anchors: true braces: forbid: non-empty max-spaces-inside-empty: 0 brackets: forbid: false max-spaces-inside: 0 max-spaces-inside-empty: 0 colons: max-spaces-before: 0 max-spaces-after: 1 commas: max-spaces-before: 0 min-spaces-after: 1 max-spaces-after: 1 comments: require-starting-space: true min-spaces-from-content: 1 comments-indentation: enable document-end: present: false document-start: present: true empty-lines: max: 2 max-start: 0 max-end: 0 empty-values: forbid-in-block-mappings: true forbid-in-block-sequences: true forbid-in-flow-mappings: true float-values: require-numeral-before-decimal: true hyphens: max-spaces-after: 1 indentation: spaces: 2 indent-sequences: false check-multi-line-strings: false key-duplicates: forbid-duplicated-merge-keys: true key-ordering: disable line-length: max: 120 allow-non-breakable-inline-mappings: true new-line-at-end-of-file: enable new-lines: type: unix octal-values: forbid-implicit-octal: true forbid-explicit-octal: true quoted-strings: check-keys: true required: only-when-needed quote-type: single trailing-spaces: enable truthy: check-keys: false allowed-values: ['true', 'false'] ================================================ FILE: Brewfile ================================================ brew "actionlint" # 1.7.11 brew "gh" # 2.87.3 brew "git" # 2.53.0 brew "ipsw" # 3.1.660 brew "markdownlint-cli2" # 0.21.0 brew "periphery" if MacOS.version >= :sequoia && `/usr/bin/arch` == "arm64" # 3.6.0 brew "shellcheck" # 0.11.0 brew "swiftformat" # 0.60.1 brew "swiftlint" # 0.63.2 brew "xcodes" # 1.6.2 brew "yamllint" # 1.38.0 ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open & welcoming environment, we as contributors & maintainers pledge to making participation in our project & our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity & expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity & orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming & inclusive language - Being respectful of differing viewpoints & experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery & unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, & personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior & are expected to take appropriate & fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right & responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, & other contributions that are not aligned to this Code of Conduct, or to temporarily or permanently ban any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces & in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined & clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at @mas-cli/admins. All complaints will be reviewed & investigated & will result in a response that is deemed necessary & appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant version 1.4]( https://www.contributor-covenant.org/version/1/4/code-of-conduct/ ). For answers to common questions about this Code of Conduct, see the [Contributor Covenant FAQ](https://www.contributor-covenant.org/faq/). ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Pull requests (PRs) are welcome from everyone. By participating in this project, you agree to abide by the [code of conduct](CODE_OF_CONDUCT.md). ## Getting Started - Ensure you have a [GitHub account](https://github.com/signup) - [Search for similar issues](https://github.com/mas-cli/mas/issues) - If one doesn't exist, [open a new issue](https://github.com/mas-cli/mas/issues/new/choose) - Select the appropriate issue template - Follow the instructions in the issue template ## Making Changes This project uses [trunk-based development](https://trunkbaseddevelopment.com), where `main` is the trunk. - [Fork the repository](https://github.com/mas-cli/mas#fork-button) on GitHub - Clone your fork: `git clone git@github.com:your-username/mas.git` - Create a topic branch instead of [working directly on `main`]( https://softwareengineering.stackexchange.com/questions/223400/when-should-i-stop-committing-to-master-on-new-projects ) - To branch a topic branch from `main` named, e.g., `feature`, run: `git checkout -b feature main` - Commit logical units - Follow the [style guide](Documentation/style.md) - Run `Scripts/format` before committing - Run `Scripts/lint` before committing, then fix all lint violations - Write tests - If you need help with tests, feel free to open a PR, then ask for help - Write [good commit messages]( https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html ) - Push your topic branch to your fork, then submit a pull request (PR) - If your PR is not ready to be merged, create a draft PR ## Releases - Release commits are tagged in the format of `v1.2.3` - Releases (including release notes) are published on the [Releases page](https://github.com/mas-cli/mas/releases) ## Becoming a Contributor To join the [team](https://github.com/orgs/mas-cli/teams/contributors), once a few of your PRs have been accepted, [open an issue](https://github.com/mas-cli/mas/issues/new) titled "Add Contributor: @YourGitHubUsername". This project was created by [@argon](https://github.com/argon), who is unable to continue contributing to this project, but must remain an owner. By becoming a contributor, you agree to the following terms: - Do not claim to be the original author - Retain [@argon](https://github.com/argon)'s name in the license; others' names may be added to it after they have made substantial contributions - Retain [@argon](https://github.com/argon)'s name (Andrew Naylor), [GitHub account](https://github.com/argon) & [X handle](https://x.com/argon) in the [README](README.md), though they may be repositioned as deemed suitable ## Project Lead Responsibilities Project leads agree to the following terms: - [@argon](https://github.com/argon) must continue to be one of the organization owners - Project leads have full control, however, over the project's future direction - If you are the sole project lead, but can no longer lead the project, either: - Find someone else to assume the project leadership who agrees to adhere to, & propagate, the existing terms - If you cannot find a new project lead: - Add an [unmaintained badge](https://unmaintained.tech) to the [README](README.md) - Message [@argon](https://github.com/argon) via [X](https://x.com/argon) or [email](mailto:argon@mkbot.net) - Transfer the project back to [@argon](https://github.com/argon) ================================================ FILE: Documentation/Sample.swift ================================================ // // Sample.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // // MARK: Types & naming /// The first letter of a type should be uppercase. /// /// Prefer structs. When a class is necessary, default to making it `final`. final class Sample { let name: String /// If the first letter of an acronym is lowercase, the entire thing should be /// lowercase. let json: Any deinit { // Clean up resources } /// If the first letter of an acronym is uppercase, the entire thing should be /// uppercase. static func decode(from json: JSON) -> Self { .init(json: json) } } /// Use `()` for void arguments & `Void` for void return types. let closure: () -> Void = { // Do nothing } /// Use `typealias` when closures are referenced in multiple places. typealias CoolClosure = (Int) -> Bool /// Use aliased parameter names when function parameters are ambiguous. func yTown(some: Int, withCallback callback: CoolClosure) -> Bool { callback(some) } /// Use `$` variable references if the closure fits on one line. let cool = yTown(5) { $0 == 6 } /// Use explicit variable names if the closure is on multiple lines. let cool = yTown(5) { foo in max(foo, 0) // … } // Strongify weak references in async closures APIClient.getAwesomeness { [weak self] result in guard let self else { return } stopLoadingSpinner() show(result) } /// Use if-let to check for not `nil` (even if using an implicitly unwrapped /// variable from an API). func someUnauditedAPI(thing: String?) { if let thing { printer.info(thing) } } /// Infer variable types instead of explicitly declaring them. let response = Response.success(Data()) func doSomeWork() -> Response { let data = Data("", .utf8) return .success(data) } switch response { case let .success(data): printer.info("The response returned successfully", data) case let .failure(error): printer.error("An error occurred:", error: error) } // MARK: Organization /// Group methods into specific extensions for each level of access control. private extension MyClass { func doSomethingPrivate() { // Do something } } // MARK: Breaking up long lines // If a guard clause requires multiple lines, chop it down, then start the else // clause on a new line guard let oneItem = somethingFailable(), let secondItem = somethingFailable2() else { return } ================================================ FILE: Documentation/style.md ================================================ # All Files - Before committing, run `Scripts/lint` to detect linting violations - Run `Scripts/format` to automatically fix many linting violations - Remove unnecessary trailing whitespace - Note that 2 trailing spaces is valid Markdown to create a line break like `
`, so those should _not_ be removed - End each file with a [single newline character]( https://unix.stackexchange.com/questions/18743/whats-the-point-in-adding-a-new-line-to-the-end-of-a-file#18789 ) ## Swift [Sample](Sample.swift) - Avoid [force unwrapping optionals]( https://blog.timac.org/2017/0628-swift-banning-force-unwrapping-optionals ) with `!` in production code - Production code is located under the [`Sources/mas`]( https://github.com/mas-cli/mas/tree/main/Sources/mas ) folder - However, force unwrapping is **encouraged** in tests for concision; tests _should_ break when any expected conditions aren't met - Prefer `struct`s over `class`es wherever possible - Default to marking classes as `final` - Prefer composition over protocol conformance over class inheritance - Break lines at 120 characters - Use tabs for indentation - Use `let` whenever possible to make immutable bindings - Name most parameters in functions & enum cases - Use trailing closures - Let the compiler infer the type whenever possible - Group computed properties below stored properties - Use a blank line above & below computed properties - Apply the capitalization of the first letter throughout an acronym or initialism - Use `()` for void arguments & `Void` for void return types - Strongify weak references instead of evaluating them multiple times ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Andrew Naylor, Ross Goldberg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Package.resolved ================================================ { "originHash" : "fe336dc5a91893be96812ec91baba97112f3407b3c398b918ac3eeebc5bc9781", "pins" : [ { "identity" : "bigint", "kind" : "remoteSourceControl", "location" : "https://github.com/attaswift/BigInt.git", "state" : { "revision" : "e07e00fa1fd435143a2dcf8b7eec9a7710b2fdfe", "version" : "5.7.0" } }, { "identity" : "chronometer", "kind" : "remoteSourceControl", "location" : "https://github.com/KittyMac/Chronometer.git", "state" : { "revision" : "99d4f4137837a28e1e294eb69ac66ad53c0934d3", "version" : "0.1.14" } }, { "identity" : "hitch", "kind" : "remoteSourceControl", "location" : "https://github.com/KittyMac/Hitch.git", "state" : { "revision" : "d3bfe4b90303653d71f5423c6ec2cbe47b75d7ed", "version" : "0.4.151" } }, { "identity" : "sextant", "kind" : "remoteSourceControl", "location" : "https://github.com/KittyMac/Sextant.git", "state" : { "revision" : "d4f794ac57a84dadacd1a0edd5d29717ef0e7b74", "version" : "0.4.38" } }, { "identity" : "spanker", "kind" : "remoteSourceControl", "location" : "https://github.com/KittyMac/Spanker.git", "state" : { "revision" : "ff05ac41aea633ca7a9fd966d3164373247ee8dd", "version" : "0.2.53" } }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { "revision" : "c5d11a805e765f52ba34ec7284bd4fcd6ba68615", "version" : "1.7.0" } }, { "identity" : "swift-atomics", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics.git", "state" : { "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", "version" : "1.3.0" } }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { "revision" : "8d9834a6189db730f6264db7556a7ffb751e99ee", "version" : "1.4.0" } }, { "identity" : "swiftsoup", "kind" : "remoteSourceControl", "location" : "https://github.com/scinfu/SwiftSoup.git", "state" : { "revision" : "dba183c96b2da4e4b80bb31b1e2e59cb9542b8fc", "version" : "2.13.0" } } ], "version" : 3 } ================================================ FILE: Package.swift ================================================ // swift-tools-version:6.2 private import PackageDescription private let swiftSettings = [ SwiftSetting .enableUpcomingFeature("ExistentialAny"), // swiftformat:disable:this indent .enableUpcomingFeature("ImmutableWeakCaptures"), .enableUpcomingFeature("InferIsolatedConformances"), .enableUpcomingFeature("InternalImportsByDefault"), .enableUpcomingFeature("MemberImportVisibility"), .enableUpcomingFeature("NonisolatedNonsendingByDefault"), .treatAllWarnings(as: .error), ] _ = Package( name: "mas", platforms: [.macOS(.v13)], products: [.executable(name: "mas", targets: ["mas"])], dependencies: [ .package(url: "https://github.com/KittyMac/Sextant.git", from: "0.4.38"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.7.0"), .package(url: "https://github.com/apple/swift-atomics.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-collections.git", from: "1.4.0"), .package(url: "https://github.com/attaswift/BigInt.git", from: "5.7.0"), .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.13.0"), ], targets: [ .plugin(name: "MASBuildToolPlugin", capability: .buildTool()), .target(name: "PrivateFrameworks"), .executableTarget( name: "mas", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "Atomics", package: "swift-atomics"), .product(name: "OrderedCollections", package: "swift-collections"), "BigInt", "PrivateFrameworks", "Sextant", "SwiftSoup", ], swiftSettings: swiftSettings, linkerSettings: [.unsafeFlags(["-F", "/System/Library/PrivateFrameworks"])], plugins: [.plugin(name: "MASBuildToolPlugin")], ), .testTarget( name: "MASTests", dependencies: ["mas"], resources: [.process("Resources")], swiftSettings: swiftSettings, ), ], swiftLanguageModes: [.v6], ) ================================================ FILE: Plugins/MASBuildToolPlugin/MASBuildToolPlugin.swift ================================================ // // MASBuildToolPlugin.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import Foundation internal import PackagePlugin @main struct MASBuildToolPlugin: BuildToolPlugin { func createBuildCommands(context: PluginContext, target _: any Target) -> [Command] { [ .prebuildCommand( displayName: "Prebuild mas", executable: context.package.directoryURL.appending(path: "Scripts/prebuild", directoryHint: .notDirectory), arguments: [context.pluginWorkDirectoryURL.path(percentEncoded: false)], environment: ProcessInfo.processInfo.environment, outputFilesDirectory: context.pluginWorkDirectoryURL, ), ] } } ================================================ FILE: README.md ================================================

![mas](mas.png)

[![current release version](https://img.shields.io/github/v/release/mas-cli/mas.svg?style=for-the-badge)](https://github.com/mas-cli/mas/releases) [![supported OS: macOS 13+](https://img.shields.io/badge/Supported_OS-macOS_13%2B-teal?style=for-the-badge)](Package.swift) [![license: MIT](https://img.shields.io/badge/license-MIT-750014.svg?style=for-the-badge)](LICENSE) [![language: Swift 6.2](https://img.shields.io/badge/language-Swift_6.2-F05138.svg?style=for-the-badge)](https://www.swift.org) [![build, test & lint status](https://img.shields.io/github/actions/workflow/status/mas-cli/mas/build-test.yaml?label=build,%20test%20%26%20lint&style=for-the-badge)]( https://github.com/mas-cli/mas/actions/workflows/build-test.yaml?query=branch%3Amain ) [![dependencies status](https://img.shields.io/librariesio/github/mas-cli/mas?style=for-the-badge)](Package.swift) mas is a command-line interface for the Mac App Store that is designed for scripting & automation.
## 📲 Installation
### 🔮 macOS 13 (Ventura) or newer
#### 🍺 Homebrew Core formula [Homebrew](https://brew.sh) is the preferred way to install: ```shell brew install mas ```
#### 🔌 MacPorts [MacPorts](https://www.macports.org/install.php) is an alternative way to install: ```shell sudo port install mas ```
### 🧮 macOS 10.11 (El Capitan) - 12 (Monterey)
#### 🍻 Homebrew tap The [mas-cli Homebrew tap](https://github.com/mas-cli/homebrew-tap) provides pre-built bottles for all macOS versions since 10.11 (El Capitan). The newest versions of mas, however, are only available for macOS 13+ (Ventura or newer). To install mas from the tap: ```shell brew install mas-cli/tap/mas ```
#### 🐙 GitHub Releases Alternatively, binaries & sources are available from [GitHub Releases](https://github.com/mas-cli/mas/releases).
## 🤳 Usage
### 🪪 App IDs Each app in the App Store has a unique integer app identifier (ADAM ID) & a unique text app identifier (bundle ID). mas commands accept either form of app ID as arguments. `mas search` & `mas list` can be used to find the ADAM IDs of apps. Alternatively, to find an app's ADAM ID: 1. Find the app in the App Store 2. Select `Share` > `Copy Link` 3. Extract the ADAM ID from the URL - e.g., extract ADAM ID `497799835` from the URL for Xcode ()
### 🛍 Info from the App Store The commands in this section do not require you to be logged into an Apple Account, neither for your macOS user nor for the App Store.
#### `mas search` `mas search ` searches by name for apps available from the App Store. Providing the `--price` flag includes each app's price in the output. ```console $ mas search Xcode 497799835 Xcode 688199928 Docs for Xcode … ```
#### `mas lookup` `mas lookup ` outputs more detailed information about an app available from the App Store. ```console $ mas lookup 497799835 Xcode 26.1.1 [Free] By: Apple Inc. Released: 2025-11-11 Minimum OS: 15.6 Size: 2,913.8 MB From: https://apps.apple.com/us/app/xcode/id497799835?mt=12&uo=4 ```
### 📚 Info from your local app library All the commands in this section require you to be logged into an Apple Account for your macOS user.
#### `mas list` `mas list` outputs all the apps on your Mac that were installed from the App Store. ```console $ mas list 497799835 Xcode (15.4) 640199958 Developer (10.6.5) 899247664 TestFlight (3.5.2) ```
#### `mas outdated` `mas outdated` outputs all apps installed from the App Store on your Mac that have pending updates. ```console $ mas outdated 497799835 Xcode (15.4 -> 16.0) 640199958 Developer (10.6.5 -> 10.6.6) ``` Run [`mas update`](#mas-update) to install pending updates.
### ⬇️ Installing apps All the commands in this section require you to be logged into an Apple Account in the App Store. > Depending on your Apple Account settings, you might need to re-authenticate in > the App Store to perform a `get`, `install`, `lucky`, or `update`, even if you > are already signed in to an Apple Account in the App Store.
#### `mas get` `mas get …` installs free apps that you haven't yet gotten/"purchased" from the App Store. [Requires root privileges to install apps](#-root-privileges). > The `purchase` alias is currently a misnomer, because it currently can only > "purchase" free apps. To purchase apps that cost money, purchase them directly > in the App Store. ```console $ mas get 497799835 ==> Downloading Xcode ==> Installed Xcode ```
#### `mas install` `mas install …` installs apps that you have already gotten or purchased from the App Store. Providing the `--force` flag re-installs the app even if it is already installed on your Mac. [Requires root privileges to install apps](#-root-privileges). ```console $ mas install 497799835 ==> Downloading Xcode ==> Installed Xcode ```
#### `mas lucky` `mas lucky ` installs the first result that would be returned by `mas search `. Like `mas install`, `mas lucky` can only install apps that have previously been gotten or purchased. [Requires root privileges to install apps](#-root-privileges). ```console $ mas lucky Xcode ==> Downloading Xcode ==> Installed Xcode ```
### 🆕 Upgrading apps All the commands in this section require you to be logged into an Apple Account in the App Store. > mas only installs/updates apps from the App Store. > > Use [`softwareupdate(8)`](https://www.unix.com/man-page/osx/8/softwareupdate) > to install system updates (e.g., Xcode Command Line Tools, Safari, etc.)
#### `mas update` `mas update` updates outdated apps installed from the App Store. Without any arguments, it updates all such apps. [Requires root privileges to update apps](#-root-privileges). ```console $ mas update Upgrading 2 outdated applications: Xcode (15.4) -> (16.0) Developer (10.6.5) -> (10.6.6) ==> Downloading Xcode ==> Installed Xcode ==> Downloading Developer ==> Installed Developer ``` Updates can be performed selectively by providing app IDs to `mas update`. ```console $ mas update 715768417 Upgrading 1 outdated application: Xcode (15.4) -> (16.0) ==> Downloading Xcode ==> Installed Xcode ```
### 🪪 App Store account management All the commands in this section interact with the Apple Account for which you are signed in to the App Store. These commands do not interact with the Apple Account for which your macOS user is signed in.
#### `mas signout` `mas signout` signs out from the current Apple Account in the App Store.
### 🫚 Root privileges Root privileges are now necessary to install/update apps from the App Store, because Apple secured `installd` on macOS 26.1+, 15.7.2+ & 14.8.2+ to fix [CVE-2025-43411](https://nvd.nist.gov/vuln/detail/CVE-2025-43411). To simplify the code, mas 4.0.0+ requires root privileges to install/update apps for all versions of macOS, even older ones for which `installd` hasn't been secured. Most users are already, or soon will be, using affected macOS versions. Root privileges were always necessary to uninstall apps from the App Store, because such apps are owned by the `root` user on macOS. mas 4.0.0+ will request root privileges if you run mas without them, so you needn't remember to use `sudo mas uninstall …` like beforehand. Root privileges can be granted by running using `sudo mas …` on the command line, or, if you run `mas` by itself without `sudo`, by entering your macOS account password when prompted by `mas`. If you choose the latter route, the supplied password is piped directly from the terminal to an external process `sudo` call in the `mas` executable; your password is never seen by any mas code, nor is it stored in any way. Any sudo credentials used or established by the `mas` executable will remain valid, pursuant to your user-configured sudo timeout settings.
## 🧩 Integrations
### 🍻 Homebrew Bundle If mas is installed: - `brew bundle dump` includes installed App Store apps in the generated `Brewfile` - Homebrew Bundle commands will process App Store apps included in a `Brewfile` See the [Homebrew Bundle documentation](https://docs.brew.sh/Brew-Bundle-and-Brewfile) for more details.
### ⚙️ Topgrade If mas is installed, running [Topgrade](https://github.com/topgrade-rs/topgrade) updates installed App Store apps.
## ⚠️ Known issues
### 💥 Broken Apple private frameworks mas uses multiple undocumented Apple private frameworks to implement much of its functionality. Over time, Apple has silently changed these frameworks, breaking some functionality, including: - [The `account` command is not supported on macOS 12 (Monterey) or newer]( https://github.com/mas-cli/mas/issues/417 ) - [The `signin` command is not supported on macOS 10.13 (High Sierra) or newer]( https://github.com/mas-cli/mas/issues/164 )
### ⏳ Eventual consistency The App Store operates on eventual consistency. [The app versions seen by various parts of mas or the App Store might be inconsistent for days](https://github.com/mas-cli/mas/issues/387).
### 📱 iOS & iPadOS apps Apple Silicon Macs can install iOS & iPadOS apps from the App Store. [mas does not yet support iOS or iPadOS apps]( https://github.com/mas-cli/mas/issues/321 ).
### 📺 `tmux` mas depends on the same XPC system services as the App Store. mas thus experiences similar problems as the pasteboard when running inside `tmux`. This [wrapper](https://github.com/ChrisJohnsen/tmux-MacOSX-pasteboard) allows pasteboard & mas to work inside `tmux`. `tmux` can be configured to always use the wrapper. Alternatively, the wrapper can be used on a one-off basis: ```shell brew install reattach-to-user-namespace reattach-to-user-namespace mas install ```
### 🤷 Undetected installed apps mas 2.0.0+ sources data for installed App Store apps from macOS's Spotlight Metadata Server (aka MDS). You can check if an App Store app is properly indexed in Spotlight: ```console ## General format: $ mdls -rn kMDItemAppStoreAdamID ## Outputs the ADAM ID if the app is indexed ## Outputs nothing if the app is not indexed ## Example: $ mdls -rn kMDItemAppStoreAdamID /Applications/WhatsApp.app 310633997 ``` If an app has been indexed in Spotlight, the path to the app can be found: ```shell mdfind 'kMDItemAppStoreAdamID = ' ``` If any App Store apps are not properly indexed, you can reindex: ```shell # Individual apps (if you know exactly what apps were incorrectly omitted): mdimport /Applications/Example.app # All apps ( is the volume optionally selected for large apps): mdimport /Applications /Volumes//Applications # All file system volumes (if neither aforementioned command solved the issue): sudo mdutil -Eai on ```
## ❗ Troubleshooting
### 🚫 Redownload not available If the following error occurs, you probably [haven't yet gotten or purchased the app from the App Store](#mas-install). > This redownload is not available for this Apple Account either because it was > bought by a different user or the item was refunded or canceled.
### ❓ Other issues If mas doesn't work as expected (e.g., apps can't be installed/updated), run `mas reset`, then try again. If the issue persists, please [file a bug]( https://github.com/mas-cli/mas/issues/new?template=01-bug-report.yaml ). All feedback is much appreciated!
## 🏗 Building mas can be built in Xcode or built by the following script: ```shell Scripts/build ``` Build output can be found in the `.build` folder in the project's root folder.
## 🧪 Testing Tests are implemented in [Swift Testing](https://developer.apple.com/xcode/swift-testing). Tests can be run by the following script: ```shell Scripts/test ```
## 📄 License Code is under the [MIT license](LICENSE). mas was originally created by Andrew Naylor ([@argon on GitHub](https://github.com/argon) / [@argon on X](https://x.com/argon)).
================================================ FILE: Scripts/_setup_script ================================================ #!/bin/zsh -Ndefgku # # Scripts/_setup_script # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Performs boilerplate setup for scripts. # builtin unalias -as setopt\ autopushd\ combiningchars\ extendedglob\ extendedhistory\ no_globalrcs\ histexpiredupsfirst\ histignorespace\ histverify\ incappendhistorytime\ interactivecomments\ pipefail\ no_rcs\ no_unset export -r HISTCHARS='!^#' export -r IFS=$' \t\n\0' export -r NULLCMD=cat export -r PAGER=cat export -r READNULLCMD=cat export -r TMPDIR="${"${TMPDIR:-/tmp/}"/%(#b)([^\/])/"${match[1]}"/}" export -r TMPPREFIX="${TMPPREFIX:-"${TMPDIR}"zsh}" unset CDPATH unset ENV unset KEYBOARD_HACK unset TMPSUFFIX unset WORDCHARS readonly mas_folder="${0:A:h:h}" if ! cd -- "${mas_folder}"; then printf $'Error: Failed to cd into mas folder: %s\n' "${mas_folder}" >&2 exit 1 fi print_notice() { [[ -v MAS_DO_NOT_PRINT_NOTICE ]] && return if [[ -t 1 ]]; then local -r prefix=$'\e[1;34m==>\e[0m' else local -r prefix='==>' fi printf $'%s %s mas %s%s\n' "${prefix}" "${1}" "${MAS_VERSION:-$(Scripts/version)}" "${${*:2}:+ (arguments: ${${(q)@:2}[*]})}" } ensure_command_available() { local -i return_status=0 local command for command in "${@}"; do if ! whence "${command}" >/dev/null; then printf $'error: %s is not installed. Run \'Scripts/bootstrap\' or \'brew install %s\'.\n' "${command}" "${command}" >&2 return_status=1 fi done return "${return_status}" } ================================================ FILE: Scripts/bootstrap ================================================ #!/bin/zsh -Ndefgku # # Scripts/bootstrap # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Installs dependencies for Scripts/format & Scripts/lint. # # Usage: bootstrap [...] # . "${0:A:h}/_setup_script" print_notice '👢 Bootstrapping' "${@}" if ! whence brew >/dev/null; then /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi brew update -q installed_formulae=("${(f)$(brew list --formulae)}") readonly -a installed_formulae bootstrap_formulae=("${(f)$(brew deps --union "${(f)$(brew bundle list --formulae)}")}") readonly -a bootstrap_formulae # shellcheck disable=SC2086 (("${#installed_formulae:*bootstrap_formulae}")) && brew upgrade --overwrite -q ${installed_formulae:*bootstrap_formulae} brew bundle upgrade -fq "${@}" brew upgrade --overwrite -q "${bootstrap_formulae[@]}" ================================================ FILE: Scripts/build ================================================ #!/bin/zsh -Ndefgku # # Scripts/build # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Builds the Swift package. # . "${0:A:h}/_setup_script" print_notice '🏗​ Building' "${@}" export -r MAS_DISTRIBUTION="${1:-}" swift build "${@:2}" ================================================ FILE: Scripts/clean ================================================ #!/bin/zsh -Ndefgku # # Scripts/clean # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Deletes the build folder & other generated files. # . "${0:A:h}/_setup_script" print_notice '🗑​ Cleaning' "${@}" zmodload zsh/zutil zparseopts -D -A received_flag D P r x if ! [[ -v 'received_flag[-P]' ]]; then swift package "${${received_flag[-D]+clean}:-reset}" fi if [[ -v 'received_flag[-r]' && -e Package.resolved ]]; then trash Package.resolved fi if [[ -v 'received_flag[-x]' && -d .swiftpm ]]; then trash .swiftpm fi ================================================ FILE: Scripts/format ================================================ #!/bin/zsh -Ndefgku # # Scripts/format # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Automatically formats & fixes style violations using various tools. # # Please keep in sync with Scripts/lint. # . "${0:A:h}/_setup_script" print_notice '🧹 Formatting' "${@}" ensure_command_available markdownlint-cli2 swiftformat swiftlint export -r MAS_DISTRIBUTION=format printf -- $'--> 🕊​ SwiftFormat\n' script -q /dev/null swiftformat --strict --markdown-files format-strict . | (grep -vxE '(?:\^D\x08{2})?Running SwiftFormat\.{3}\r|Reading (?:config|swift-version) file at .*|\x1b\[32mSwiftFormat completed in \d+(?:\.\d+)?s\.\x1b\[0m\r|0/\d+ files formatted\.\r' || true) printf -- $'--> 🦅 SwiftLint\n' swiftlint --fix --quiet --reporter relative-path printf -- $'--> 〽️ Markdown\n' # shellcheck disable=SC1036 markdownlint-cli2 --fix -- ***/*.md(.) printf -- $'--> 🚷 Non-Executables\n' readonly -a non_executables=(Scripts/***/*(N.^f+111)) if (("${#non_executables[@]}")); then chmod -vv a+x "${non_executables[@]}" exit 1 fi ================================================ FILE: Scripts/generate_manual ================================================ #!/bin/zsh -Ndefgku # # Scripts/generate_manual # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Generates man page. # . "${0:A:h}/_setup_script" print_notice '📖 Generating man pages for' "${@}" export -r MAS_DISTRIBUTION="${1:-}" swift package generate-manual "${@:2}" ================================================ FILE: Scripts/generate_token ================================================ #!/bin/zsh -Ndefgku # # Scripts/generate_token # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Generates a GitHub App installation access token for GitHub Workflows. # . "${0:A:h}/_setup_script" readonly header=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 readonly payload="${${$(printf '{"iss":%s,"iat":%s,"exp":%s}' "${TOKEN_APP_ID}" "$(("$(date +%s)" - 60))"\ "$(("$(date +%s)" + 540))" | base64)//[=$'\n']}//\/+/_-}" # shellcheck disable=SC1036,SC1072,SC1073 curl\ -sX POST\ -H "Authorization: Bearer ${header}.${payload}.${${$(printf %s "${header}.${payload}" | openssl dgst -sha256 -sign =(printf %s "${TOKEN_APP_PRIVATE_KEY}") | base64)//[=$'\n']}//\/+/_-}"\ -H 'Accept: application/vnd.github+json'\ "https://api.github.com/app/installations/${TOKEN_APP_INSTALLATION_ID}/access_tokens" | jq -r .token ================================================ FILE: Scripts/lint ================================================ #!/bin/zsh -Ndfgku # # Scripts/lint # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Reports style violations without making any modifications to the code. # # Please keep in sync with Scripts/format. # # shellcheck disable=SC1036,SC1056,SC1072 . "${0:A:h}/_setup_script" print_notice '🚨 Linting' "${@}" ensure_command_available actionlint git markdownlint-cli2 shellcheck swiftformat swiftlint yamllint || exit [[ "$(/usr/bin/arch)" = arm64 && "${$(sw_vers -productVersion)%%.*}" -ge 15 ]] integer -r can_use_periphery="$((! ?))" # shellcheck disable=SC1073,SC1083 ((can_use_periphery)) && { ensure_command_available periphery || exit } zmodload zsh/zutil zparseopts -D -A received_flag P export -r MAS_DISTRIBUTION=lint integer exit_status=0 printf -- $'--> 🕊​ SwiftFormat\n' script -q /dev/null swiftformat --lint --markdown-files format-strict . | (grep -vxE '(?:\^D\x08{2})?Running SwiftFormat\.{3}\r|\(lint mode - no files will be changed\.\)\r|Reading (?:config|swift-version) file at .*|\x1b\[32mSwiftFormat completed in \d+(?:\.\d+)?s\.\x1b\[0m\r|0/\d+ files require formatting\.\r|Source input did not pass lint check\.\r' || true) ((exit_status |= ${?})) printf -- $'--> 🦅 SwiftLint\n' swiftlint --strict --quiet --reporter relative-path ((exit_status |= ${?})) if ((can_use_periphery)) && ! [[ -v 'received_flag[-P]' ]]; then printf -- $'--> 🌀 Periphery\n' periphery scan --exclude-tests | (grep -vxE '(?:\x1b\[0;1;32m|\^D\x08{2})\* (?:\x1b\[0;0m\x1b\[0;1m)?No unused code detected\.(?:\x1b\[0;0m)?\r?' || true) ((exit_status |= ${?})) printf -- $'--> 🌀 Periphery Tests\n' periphery scan | (grep -vxE '(?:\x1b\[0;1;32m|\^D\x08{2})\* (?:\x1b\[0;0m\x1b\[0;1m)?No unused code detected\.(?:\x1b\[0;0m)?\r?' || true) ((exit_status |= ${?})) fi printf -- $'--> 〽️ Markdown\n' markdownlint-cli2 -- ***/*.md(.) ((exit_status |= ${?})) printf -- $'--> 📝 YAML\n' yamllint -s . ((exit_status |= ${?})) printf -- $'--> 🌳 Git\n' git diff --check ((exit_status |= ${?})) printf -- $'--> 💤 Zsh\n' for script in Scripts/***/*(.); do /bin/zsh -n "${script}" ((exit_status |= ${?})) done printf -- $'--> 🐙 ActionLint\n' actionlint -shellcheck shellcheck ((exit_status |= ${?})) printf -- $'--> 🐚 ShellCheck\n' shellcheck -s bash -o all -e SC1009,SC1088,SC2296,SC2298,SC2299,SC2300,SC2301,SC2312 -a -P SCRIPTDIR Scripts/***/*(.) ((exit_status |= ${?})) printf -- $'--> 🚷 Non-Executables\n' readonly -a non_executables=(Scripts/***/*(N.^f+111)) if (("${#non_executables[@]}")); then printf $'%s\n' "${non_executables[@]}" ((exit_status |= 1)) fi exit "${exit_status}" ================================================ FILE: Scripts/package ================================================ #!/bin/zsh -Ndefgku # # Scripts/package # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Builds .pkg installer. # . "${0:A:h}/_setup_script" print_notice '📦 Packaging installer for' "${@}" export MAS_DO_NOT_PRINT_NOTICE= Scripts/build "${1:-}" -c release "${@:2}" unset MAS_DO_NOT_PRINT_NOTICE readonly build_folder=.build readonly destination_folder="${build_folder}/destination" readonly installation_folder=/usr/local/opt/mas readonly installation_staging_folder="${destination_folder}${installation_folder}" readonly usr_local_bin_staging_folder="${destination_folder}/usr/local/bin" version="$(Scripts/version)" readonly version swift package generate-manual mkdir -p "${installation_staging_folder}/bin" mkdir -p "${installation_staging_folder}/etc/bash_completion.d" mkdir -p "${installation_staging_folder}/share/fish/vendor_completions.d" mkdir -p "${installation_staging_folder}/share/man/man1" mkdir -p "${usr_local_bin_staging_folder}" cp LICENSE README.md "${installation_staging_folder}" cp contrib/completion/mas-completion.bash "${installation_staging_folder}/etc/bash_completion.d/mas" cp contrib/completion/mas.fish "${installation_staging_folder}/share/fish/vendor_completions.d/mas.fish" ln -f "$(swift build -c release --show-bin-path "${@:2}")/mas" "${installation_staging_folder}/bin/mas" ln -f .build/plugins/GenerateManual/outputs/mas/mas.1 "${installation_staging_folder}/share/man/man1/mas.1" ln -fs "${installation_folder}/bin/mas" "${usr_local_bin_staging_folder}/mas" archs=("${(s: :n)$(lipo -archs "${installation_staging_folder}/bin/mas")}") # shellcheck disable=SC2034 readonly -a archs pkgbuild\ --identifier io.github.mas-cli.mas\ --install-location /\ --version "${version}"\ --root "${destination_folder}"\ "${build_folder}/mas.pkg" # shellcheck disable=SC1036 productbuild\ --distribution =(<<<\ ' mas mas.pkg ' )\ --package-path "${build_folder}"\ "${build_folder}/mas-${version//\//_}-${(j:-:)archs[@]}.pkg" ================================================ FILE: Scripts/prebuild ================================================ #!/bin/zsh -Ndefgku # # Scripts/prebuild # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Prebuilds the Swift package. # . "${0:A:h}/_setup_script" print_notice '🎬 Prebuilding' "${@}" # Generate Swift file containing build information. # shellcheck disable=SC1102 printf '// // MAS+BuildInformation.swift // mas // // Copyright © %s mas-cli. All rights reserved. // extension MAS { static let version = "%s" static let distribution = "%s" static let gitOrigin = "%s" static let gitRevision = "%s" static let swiftVersion = "%s" static let swiftDriverVersion = "%s" } '\ "$(date +%Y)"\ "$(Scripts/version)"\ "${MAS_DISTRIBUTION:-unknown}"\ "$(git remote get-url origin)"\ "$(git rev-parse HEAD)"\ "${${${$(swift --version 2>/dev/null)##( |[[:alpha:]])##}%%$'\n'*}:-unknown}"\ "${${(SM)$(swift --version 2>&1 >/dev/null)##[[:digit:]]([[:digit:]]|.)##}:-unknown}"\ >"${1}/MAS+BuildInformation.swift" ================================================ FILE: Scripts/release_cancel ================================================ #!/bin/zsh -Ndefgku # # Scripts/release_cancel # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Cancels a GitHub draft release. # # Usage: release_cancel # . "${0:A:h}/_setup_script" readonly tag="${1}" export MAS_VERSION="${tag#v}" print_notice '❌ Canceling release for' "${@}" unset MAS_VERSION bump_url="$(gh release -R https://github.com/mas-cli/mas download "${tag}" -p bump.url -O - 2>/dev/null || true)" readonly bump_url if [[ -n "${bump_url}" ]]; then printf $'\n' gh pr close "${bump_url}" -d printf $'\n' else printf $'\nNo tap formula bump PR URL found for draft release tag %s\n\n' "${tag}" fi gh release -R https://github.com/mas-cli/mas delete "${tag}" --cleanup-tag -y ================================================ FILE: Scripts/release_start ================================================ #!/bin/zsh -Ndefgku # # Scripts/release_start # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Starts the release process by creating & pushing a signed annotated version tag to the GitHub mas-cli/mas repo. # # Usage: release_start [] # # must match the following zsh pattern: # # ^v[[:digit:]]+(\.[[:digit:]]+)*(-(alpha|beta|rc)\.[[:digit:]]+)?$ # # must may contain at most 64 characters, which may be only visible characters or spaces # # if optional value supplied, must be on the main branch; defaults to HEAD # . "${0:A:h}/_setup_script" readonly tag="${1}" readonly title="${2}" readonly ref="${3:-HEAD}" export MAS_VERSION="${tag#v}" print_notice '🚀 Starting release for' "${@}" unset MAS_VERSION printf $'\n' if [[ ! "${tag}" =~ '^v[[:digit:]]+(\.[[:digit:]]+)*(-(alpha|beta|rc)\.[[:digit:]]+)?$' ]]; then printf $'%s is not a valid version tag\n' "${tag}" >&2 exit 1 fi if (("${#title}" > 64)); then printf $'\'%s\' is too long for a version title, which may contain at most 64 characters\n' "${(q)title}" >&2 exit 2 fi if [[ "${title}" =~ [[:cntrl:]$'\t\n\r'] ]]; then printf $'\'%s\' is not a valid version title, which may contain only visible characters or spaces\n' "${(q)title}" >&2 exit 3 fi # shellcheck disable=SC1027,SC1036,SC1072,SC1073 if [[ "${title}" = (' '*|*' ') ]]; then printf $'\'%s\' is not a valid version title, which may not begin or end with a space\n' "${(q)title}" >&2 exit 4 fi if ! git merge-base --is-ancestor "${ref}" upstream/main; then printf $'%s is not a valid reference for a version, which must be on the upstream/main branch\n' "${ref}" >&2 exit 5 fi git tag -s "${tag}" -m "${title}" "${ref}" printf $'Created version tag %s with title \'%s\' for reference %s\n\n' "${tag}" "${(q)title}" "${ref}" git push upstream tag "${tag}" ================================================ FILE: Scripts/setup_workflow_repo ================================================ #!/bin/zsh -Ndefgku # # Scripts/setup_workflow_repo # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Sets up the repo for use in a GitHub workflow. # . "${0:A:h}/_setup_script" # shellcheck disable=SC2066 for branch in "${(f)"$(git for-each-ref refs/remotes/origin --format='%(if)%(symref)%(then)%(else)%(refname:strip=-1)%(end)')":#}"; do git branch --track "${branch}" "origin/${branch}" >/dev/null 2>&1 || true done ================================================ FILE: Scripts/test ================================================ #!/bin/zsh -Ndefgku # # Scripts/test # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Runs mas tests. # . "${0:A:h}/_setup_script" print_notice '🧪 Testing' "${@}" export -r MAS_DISTRIBUTION=test swift test --disable-xctest -q "${@}" ================================================ FILE: Scripts/update_dependencies ================================================ #!/bin/zsh -GNdefgku # # Scripts/update_dependencies # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Update dependencies. # . "${0:A:h}/_setup_script" print_notice '⬆️​ Updating dependencies for' "${@}" printf -- $'--> 🍺 Homebrew\n' export MAS_DO_NOT_PRINT_NOTICE= Scripts/bootstrap unset MAS_DO_NOT_PRINT_NOTICE printf -- $'--> 🕊​ Swift\n' swift package update ================================================ FILE: Scripts/update_headers ================================================ #!/bin/zsh -GNdefgku # # Scripts/update_headers # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Generates headers for Apple private frameworks. # . "${0:A:h}/_setup_script" ensure_command_available ipsw zmodload zsh/zutil zparseopts -D -A received_flag C X readonly -a frameworks_globs=("${@:-*(/)}") cd Sources/PrivateFrameworks/include for framework in ${~frameworks_globs[@]}; do ! [[ -v 'received_flag[-X]' ]] && ipsw class-dump --headers --output . /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e -- "${framework}" # shellcheck disable=SC1046,SC1047,SC1072,SC1073 if ! [[ -v 'received_flag[-C]' ]]; then cd -- "${framework}" # shellcheck disable=SC1036 sed -Ei ''\ -e 's!^// -!// -!g'\ -e 's!^// !//!g'\ -e '/^#(define|endif|ifndef).*/d'\ -e '/^ *\/\*.*\*\/$/d'\ -e '/^@import Foundation;$/d'\ -e 's/^ /\t/g'\ -e 's/_Bool/BOOL/g'\ -e 's/^- \(id\)description;$/- \(nonnull NSString \*\)description;/g'\ -e 's/^- \(void\)encodeWithCoder:\(id\)coder;$/- \(void\)encodeWithCoder:\(nullable NSCoder \*\)coder;/g'\ -e 's/^- \(id\)initWithCoder:\(id\)coder;$/- \(nonnull instancetype\)initWithCoder:\(nullable NSCoder \*\)coder;/g'\ -e 's/^- \(BOOL\)isEqual:\(id\)equal;$/- \(BOOL\)isEqual:\(nullable id\)object;/g'\ -e 's/^\+ \(id\)interface;$/\+ \(nonnull NSXPCInterface \*\)interface;/g'\ -e 's/\(copy\)/\(copy, nullable\)/g'\ -e 's/\(copy, nonatomic\)/\(copy, nonatomic, nullable\)/g'\ -e 's/\(id\)client/\(nullable ISStoreClient \*\)client/g'\ -e 's/\(id\)dictionary/\(nullable NSDictionary \*\)dictionary/g'\ -e 's/\(id\)init/\(nonnull instancetype)init/g'\ -e 's/\(retain\)/\(retain, nullable\)/g'\ -e 's/\(retain, nonatomic\)/\(retain, nonatomic, nullable\)/g'\ -e 's/\(id\)dsid/\(nullable NSNumber \*\)dsID/g'\ -e 's/NSArray \*downloads/NSArray\ \*downloads/g'\ -e 's/\(id\)productID/\(nonnull NSNumber \*\)productID/g'\ -e 's/- \(id\)copyWithZone:\(struct _NSZone \*\)zone;/- \(nonnull instancetype\)copyWithZone:\(nullable struct _NSZone \*\)zone;/g'\ -e 's/\(id\)progress/\(nullable SSOperationProgress \*\)progress/g'\ -e 's/\(id\)url/\(nullable NSURL \*\)url/g'\ -e 's/:\(id\)error/:\(nullable NSError \*\)error/g'\ -e 's/NSObject\ \*/dispatch_queue_t /g'\ -e 's/NSObject\ \*/dispatch_source_t /g'\ -e 's!id /\* block \*/!UnknownBlock!g'\ ***/*.h(.N) for header in ***/*.h(.N); do sed -i '' -e ':a' -e '/^\n*$/{$d;N;ba' -e '}' "${header}" done cd .. fi done ================================================ FILE: Scripts/version ================================================ #!/bin/zsh -Ndefgku # # Scripts/version # mas # # Copyright © 2025 mas-cli. All rights reserved. # # Outputs the mas version. # . "${0:A:h}/_setup_script" branch="${"$(git rev-parse --abbrev-ref HEAD)":/main}" if [[ "${branch}" = HEAD ]]; then if ! git show-ref --verify --quiet refs/heads/main || git merge-base --is-ancestor HEAD main; then readonly branch= else readonly branch="${"${"${(fnO)"$(git branch --contains HEAD --format '%(ahead-behind:HEAD) %(refname:short)')"}"[1]}"##* }" fi else readonly branch fi printf $'%s%s%s\n'\ "${"$(git describe --tags 2>/dev/null)"#v}"\ "${branch:+"-${branch}"}"\ "${"$(git diff-index HEAD --;git ls-files --exclude-standard --others)":+"${MAS_DIRTY_INDICATOR-+}"}" ================================================ FILE: Sources/PrivateFrameworks/PrivateFrameworks.c ================================================ // Xcode will not build without this file causing an object file to be compiled. ================================================ FILE: Sources/PrivateFrameworks/include/CommerceKit/CKDownloadDirectory.h ================================================ // // CKDownloadDirectory.h // mas // // Copyright © 2018 mas-cli. All rights reserved. // NSString * _Nonnull CKDownloadDirectory(NSString * _Nullable target); ================================================ FILE: Sources/PrivateFrameworks/include/CommerceKit/CKDownloadQueue.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface CKDownloadQueue : CKServiceInterface { NSMutableDictionary *_downloadsByItemID; NSLock *_downloadsLock; id _observerToken; NSLock *_tokenLock; } + (nonnull instancetype)sharedDownloadQueue; @property (retain, nonatomic, nullable) NSMutableDictionary *downloadQueueObservers; // @property (readonly, nonatomic, nullable) NSArray *downloads; // Unverified generic type @property (retain, nonatomic, nullable) CKDownloadQueueClient *sharedObserver; - (void)addDownload:(nonnull SSDownload *)download; - (nonnull NSString *)addObserver:(nullable id )observer; - (nonnull NSString *)addObserver:(nullable id )observer forDownloadTypes:(long long)downloadTypes; - (nonnull NSString *)addObserverForDownloadTypes:(long long)downloadTypes withBlock:(nullable UnknownBlock)block; - (BOOL)cacheReceiptDataForDownload:(nullable SSDownload *)download; - (void)cancelDownload:(nullable SSDownload *)download promptToConfirm:(BOOL)promptToConfirm askToDelete:(BOOL)askToDelete; - (void)checkStoreDownloadQueueForAccount:(nullable ISStoreAccount *)account; // Unverified account type - (void)connectionWasInterrupted; - (nullable SSDownload *)downloadForItemIdentifier:(unsigned long long)identifier; // Unverified return type - (void)fetchIconForItemIdentifier:(unsigned long long)identifier atURL:(nullable NSURL *)url replyBlock:(nonnull UnknownBlock)block; - (nonnull instancetype)initWithStoreClient:(nullable ISStoreClient *)client; // Unverified client type - (void)lockApplicationsForBundleID:(nullable NSString *)bundleID; // Unverified bundleID type - (void)lockedApplicationTriedToLaunchAtPath:(nullable NSString *)path; // Unverified path type - (void)pauseDownloadWithItemIdentifier:(unsigned long long)identifier; - (void)performedIconAnimationForDownloadWithIdentifier:(unsigned long long)identifier; - (void)removeDownloadWithItemIdentifier:(unsigned long long)identifier; - (void)removeObserver:(nullable NSString *)observerUUID; // Unverified observerUUID type - (void)resumeDownloadWithItemIdentifier:(unsigned long long)identifier; - (void)unlockApplicationsWithBundleIdentifier:(nullable NSString *)bundleID; // Unverified bundleID type @end ================================================ FILE: Sources/PrivateFrameworks/include/CommerceKit/CKDownloadQueueObserver-Protocol.h ================================================ // // CKDownloadQueueObserver-Protocol.h // mas // // Copyright © 2018 mas-cli. All rights reserved. // @protocol CKDownloadQueueObserver @required - (void)downloadQueue:(nonnull CKDownloadQueue *)queue changedWithAddition:(nonnull SSDownload *)download; - (void)downloadQueue:(nonnull CKDownloadQueue *)queue changedWithRemoval:(nonnull SSDownload *)download; - (void)downloadQueue:(nonnull CKDownloadQueue *)queue statusChangedForDownload:(nonnull SSDownload *)download; @optional @end ================================================ FILE: Sources/PrivateFrameworks/include/CommerceKit/CKPurchaseController.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // typedef void (^SSPurchaseCompletion)(SSPurchase * _Nonnull purchase, BOOL completed, NSError * _Nullable error, SSPurchaseResponse * _Nullable response); @interface CKPurchaseController : CKServiceInterface { NSArray *_adoptionEligibleItems; NSNumber *_adoptionErrorNumber; NSNumber *_adoptionServerStatus; NSMutableArray *_purchases; NSMutableArray *_rejectedPurchases; } + (void)setNeedsSilentMachineAuthorization:(BOOL)needsSilentMachineAuthorization; + (nonnull instancetype)sharedPurchaseController; @property (copy, nullable) void (^dialogHandler)(CKDialog * _Nullable); // Unverified type - (void)_performVPPReceiptRenewal; - (BOOL)adoptionCompletedForBundleID:(nullable NSString *)bundleID; - (void)cancelPurchaseWithProductID:(nullable NSNumber *)productID; - (void)checkServerDownloadQueue; - (void)performPurchase:(nonnull SSPurchase *)purchase withOptions:(unsigned long long)options completionHandler:(nullable SSPurchaseCompletion)handler; - (nullable SSPurchase *)purchaseInProgressForProductID:(nullable NSNumber *)productID; // Unverified return type - (nullable NSArray *)purchasesInProgress; // Unverified return type - (void)resumeDownloadForPurchasedProductID:(nullable NSNumber *)productID; // Unverified productID type - (void)startPurchases:(nullable NSArray *)purchases shouldStartDownloads:(BOOL)shouldStartDownloads eventHandler:(nullable void (^)(NSArray * _Nonnull))handler; // Unverified purchases generic type / handler type - (void)startPurchases:(nullable NSArray *)purchases withOptions:(unsigned long long)options completionHandler:(nullable void (^)(NSArray * _Nonnull))handler; // Unverified purchases type / handler parameter type @end ================================================ FILE: Sources/PrivateFrameworks/include/CommerceKit/CKServiceInterface.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface CKServiceInterface : ISServiceProxy @end ================================================ FILE: Sources/PrivateFrameworks/include/CommerceKit/CommerceKit.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @import StoreFoundation; @class CKDialog, CKDownloadQueueClient; @protocol CKDownloadQueueObserver; #import "CKServiceInterface.h" #import "CKDownloadDirectory.h" #import "CKDownloadQueue.h" #import "CKDownloadQueueObserver-Protocol.h" #import "CKPurchaseController.h" ================================================ FILE: Sources/PrivateFrameworks/include/CommerceKit/module.modulemap ================================================ module CommerceKit [system] [no_undeclared_includes] { requires macos, objc use StoreFoundation link framework "CommerceKit" umbrella header "CommerceKit.h" export * } ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/ISAccountService-Protocol.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @protocol ISAccountService @required - (void)accountWithAppleID:(nullable NSString *)appleID replyBlock:(nonnull void (^)(ISStoreAccount * _Nullable))block; // Unverified appleID type / block parameter types - (void)accountWithDSID:(nullable NSNumber *)dsID replyBlock:(nonnull void (^)(ISStoreAccount * _Nullable))block; // Unverified dsID type / block parameter types - (void)addAccount:(nullable ISStoreAccount *)account; // Unverified account type - (void)addAccountStoreObserver:(nullable id )observer; // Unverified observer type - (void)addURLBagObserver:(nullable id )observer; - (void)authIsExpiredWithReplyBlock:(nonnull void (^)(BOOL))block; // Unverified block parameter types - (void)dictionaryForDSID:(nullable NSNumber *)dsID withReplyBlock:(nonnull void (^)(NSDictionary * _Nullable))block; // Unverified dsID type / block parameter types - (void)dictionaryWithReplyBlock:(nonnull void (^)(NSDictionary * _Nonnull))block; // Unverified block parameter types - (void)generateTouchIDHeadersForDSID:(nullable NSNumber *)dsID challenge:(nullable NSString *)challenge caller:(nullable id)caller replyBlock:(nonnull void (^)(NSDictionary * _Nonnull, NSError * _Nullable))block; // Unverified dsID type / challenge type / caller type / block parameter types - (void)getTouchIDPreferenceWithReplyBlock:(nonnull void (^)(BOOL, ISStoreAccount * _Nullable, NSError * _Nullable))block; // Unverified block parameter types - (void)httpHeadersForURL:(nullable NSURL *)url forDSID:(nullable NSNumber *)dsID includeADIHeaders:(BOOL)includeADIHeaders withReplyBlock:(nonnull void (^)(NSDictionary * _Nonnull))block; // Unverified url type / dsID type / block parameter types - (void)iCloudDSIDReplyBlock:(nonnull void (^)(NSString * _Nullable))block; // Unverified block parameter types - (void)invalidateAllBags; - (void)isValidWithReplyBlock:(nonnull void (^)(BOOL))block; // Unverified block parameter types - (void)loadURLBagWithType:(unsigned long long)type replyBlock:(nonnull void (^)(BOOL, BOOL, NSError * _Nullable))block; // Unverified block parameter types - (void)needsSilentADIActionForURL:(nullable NSURL *)url dsID:(nullable NSNumber *)dsID withReplyBlock:(nonnull void (^)(BOOL))block; // Unverified url type / dsID type / block parameter types - (void)needsSilentADIActionForURL:(nullable NSURL *)url withReplyBlock:(nonnull void (^)(BOOL))block; // Unverified url type / block parameter types - (void)primaryAccountWithReplyBlock:(nonnull void (^)(ISStoreAccount * _Nullable))block; // Unverified block parameter types - (void)processURLResponse:(nullable NSURLResponse *)urlResponse forRequest:(nullable NSURLRequest *)request; // Unverified urlResponse type / request type - (void)processURLResponse:(nullable NSURLResponse *)urlResponse forRequest:(nullable NSURLRequest *)request dsID:(nullable NSNumber *)dsID; // Unverified urlResponse type / request type / dsID type - (void)regexWithKey:(nullable NSString *)key dsID:(nullable NSNumber *)dsID matchesString:(nullable NSString *)string replyBlock:(nonnull void (^)(BOOL))block; // Unverified key type / dsID type / string type / block parameter types - (void)regexWithKey:(nullable NSString *)key matchesString:(nullable NSString *)string replyBlock:(nonnull void (^)(BOOL))block; // Unverified key type / string type / block parameter types - (void)removeAccountStoreObserver:(nullable id )observer; // Unverified observer type - (void)removeURLBagObserver:(nullable id )observer; // Unverified observer type - (void)retailStoreDemoModeReplyBlock:(nonnull void (^)(BOOL, NSString * _Nullable, NSString * _Nullable, BOOL))block; // Unverified block parameter types - (void)setStoreFrontID:(nullable NSString *)storefrontID; // Unverified storefrontID type - (void)setTouchIDState:(long long)touchIDState forDSID:(nullable NSNumber *)dsID replyBlock:(nonnull void (^)(BOOL, NSError * _Nullable))block; // Unverified dsID type / block parameter types - (void)shouldSendGUIDWithRequestForURL:(nullable NSURL *)url withReplyBlock:(nonnull void (^)(BOOL))block; // Unverified url type / block parameter types - (void)signOut; - (void)storeFrontWithReplyBlock:(nonnull void (^)(NSString * _Nonnull))block; // Unverified block parameter types - (void)updateTouchIDSettingsForDSID:(nullable NSNumber *)dsID replyBlock:(nonnull void (^)(BOOL, NSError * _Nullable))block; // Unverified dsID type / block parameter types - (void)urlIsTrustedByURLBag:(nullable NSURL *)urlBag dsID:(nullable NSNumber *)dsID withReplyBlock:(nonnull void (^)(BOOL))block; // Unverified urlBag type / dsID type / block parameter types - (void)urlIsTrustedByURLBag:(nullable NSURL *)urlBag withReplyBlock:(nonnull void (^)(BOOL))block; // Unverified urlBag type / block parameter types - (void)valueForURLBagKey:(nullable NSString *)bagKey dsID:(nullable NSNumber *)dsID withReplyBlock:(nonnull void (^)(NSURL * _Nullable))block; // Unverified bagKey type / dsID type / block parameter types - (void)valueForURLBagKey:(nullable NSString *)bagKey withReplyBlock:(nonnull void (^)(NSURL * _Nullable))block; // Unverified bagKey type / block parameter types @optional @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/ISServiceProxy.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface ISServiceProxy : NSObject + (nonnull instancetype)genericSharedProxy; + (void)initialize; @property (readonly, nonatomic, nonnull) id accountService; @property (readonly, nonatomic, nonnull) id assetService; @property (readonly, nonatomic, nonnull) id downloadService; @property (readonly, nonatomic, weak, nullable) id exportedObject; @property (readonly, nonatomic, nullable) Protocol *exportedProtocol; @property (retain, nonatomic, nullable) ISStoreClient *storeClient; @property (readonly, nonatomic, nonnull) id transactionService; @property (readonly, nonatomic, nonnull) id uiService; - (void)accountServiceSynchronousBlock:(nonnull UnknownBlock)block; - (nonnull id )accountServiceWithErrorHandler:(nullable UnknownBlock)handler; - (void)assetServiceSynchronousBlock:(nonnull UnknownBlock)block; - (nonnull id )assetServiceWithErrorHandler:(nullable UnknownBlock)handler; - (void)connectionWasInterrupted; - (nonnull NSXPCConnection *)connectionWithServiceName:(nonnull NSString *)serviceName protocol:(nonnull Protocol *)protocol isMachService:(BOOL)isMachService; - (void)downloadServiceSynchronousBlock:(nonnull UnknownBlock)block; - (nonnull id )downloadServiceWithErrorHandler:(nullable UnknownBlock)handler; - (nonnull instancetype)initWithStoreClient:(nullable ISStoreClient *)client; // Unverified client type - (nonnull id)objectProxyForServiceName:(nonnull NSString *)serviceName protocol:(nonnull id)protocol interfaceClassName:(nullable NSString *)interfaceClassName isMachService:(BOOL)isMachService errorHandler:(nullable UnknownBlock)handler; - (void)performSynchronousBlock:(nonnull UnknownBlock)block withServiceName:(nonnull NSString *)serviceName protocol:(nonnull Protocol *)protocol isMachService:(BOOL)isMachService interfaceClassName:(nullable NSString *)interfaceClassName; - (void)registerForInterrptionNotification; - (void)transactionServiceSynchronousBlock:(nonnull UnknownBlock)block; - (nonnull id )transactionServiceWithErrorHandler:(nullable UnknownBlock)handler; - (void)uiServiceSynchronousBlock:(nonnull UnknownBlock)block; - (nonnull id )uiServiceWithErrorHandler:(nullable UnknownBlock)handler; @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/ISStoreAccount.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface ISStoreAccount : NSObject { NSTimer *_tokenInvalidTimer; } + (nullable NSNumber *)dsidFromPlistValue:(nullable id)value; + (nonnull NSDictionary *)migratePersistedStoreDictionary:(nullable NSDictionary *)dictionary; + (BOOL)supportsSecureCoding; @property long long URLBagType; @property (readonly, getter=isAuthenticated) BOOL authenticated; @property (copy, nullable) NSString *creditString; @property (copy, nullable) NSNumber *dsID; @property (copy, nullable) NSString *identifier; @property BOOL isManagedStudent; @property BOOL isSignedIn; @property long long kind; @property (copy, nullable) NSString *password; @property (readonly, getter=isPrimary) BOOL primary; @property (retain, nullable) NSString *storeFront; @property (copy, nullable) NSString *token; @property (retain, nullable) NSTimer *tokenExpirationTimer; @property (retain, nullable) NSDate *tokenIssuedDate; @property long long touchIDState; - (nonnull NSString *)description; - (void)encodeWithCoder:(nullable NSCoder *)coder; - (long long)getTouchIDState; - (BOOL)hasValidStrongToken; - (nonnull instancetype)initWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)initWithPersistedStoreDictionary:(nullable NSDictionary *)dictionary; - (void)mergeValuesFromAuthenticationResponse:(nullable ISAuthenticationResponse *)response; - (nonnull NSDictionary *)persistedStoreDictionary; - (void)resetTouchIDState; - (double)strongTokenValidForSecond; @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/SSDownload.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface SSDownload : NSObject { BOOL _needsPreInstallValidation; } + (BOOL)supportsSecureCoding; @property (copy, nullable) NSNumber *accountDSID; @property (copy, nonatomic, nullable) NSArray *assets; // Unverified generic type @property (copy, nullable) NSString *cancelURLString; @property (copy, nullable) NSString *customDownloadPath; @property BOOL didAutoUpdate; @property unsigned long long downloadType; @property BOOL installAfterLogout; @property (copy, nullable) NSString *installPath; @property BOOL isInServerQueue; @property (copy, nonatomic, nullable) SSDownloadMetadata *metadata; @property BOOL needsDisplayInDock; @property (copy, nullable) NSURL *relaunchAppWithBundleURL; @property BOOL skipAssetDownloadIfNotAlreadyOnDisk; @property BOOL skipInstallPhase; @property (retain, nonatomic, nullable) SSDownloadStatus *status; - (void)cancel; - (void)cancelWithPrompt:(BOOL)prompt; - (void)cancelWithPrompt:(BOOL)prompt storeClient:(nullable ISStoreClient *)client; - (void)cancelWithStoreClient:(nullable ISStoreClient *)client; - (void)encodeWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)init; - (nonnull instancetype)initWithAssets:(nullable NSArray *)assets metadata:(nullable SSDownloadMetadata *)metadata; // Unverified assets type / metadata type - (nonnull instancetype)initWithCoder:(nullable NSCoder *)coder; - (BOOL)isEqual:(nullable id)object; - (void)pause; - (void)pauseWithStoreClient:(nullable ISStoreClient *)client; - (nullable SSDownloadAsset *)primaryAsset; - (void)resume; - (void)resumeWithStoreClient:(nullable ISStoreClient *)client; - (void)setUseUniqueDownloadFolder:(BOOL)useUniqueDownloadFolder; @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/SSDownloadMetadata.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface SSDownloadMetadata : NSObject { NSLock *_lock; } + (BOOL)supportsSecureCoding; @property (readonly, nonnull) NSNumber *ageRestriction; @property BOOL animationExpected; @property (retain, nullable) NSString *appleID; @property (readonly, nonnull) NSString *applicationIdentifier; @property BOOL artworkIsPrerendered; @property (readonly, nonnull) NSArray *assets; // Unverified generic type @property (readonly, nullable) NSString *bundleDisplayName; @property (retain, nullable) NSString *bundleIdentifier; @property (readonly, nullable) NSString *bundleShortVersionString; @property (retain, nullable) NSString *bundleVersion; @property (retain, nullable) NSString *buyParameters; @property (readonly, nullable) NSNumber *collectionID; @property (retain, nullable) NSString *collectionName; @property (retain, nullable) NSDictionary *dictionary; @property (retain, nullable) NSString *downloadKey; @property (retain, nullable) NSNumber *durationInMilliseconds; @property (retain, nullable) NSData *epubRightsData; @property (readonly) BOOL extractionCanBeStreamed; @property (retain, nullable) NSString *fileExtension; @property (retain, nullable) NSString *genre; @property (readonly, nullable) NSNumber *iapContentSize; @property (readonly, nullable) NSString *iapContentVersion; @property (retain, nullable) NSString *iapInstallPath; @property (retain, nullable) NSData *ipaInstallBookmarkData NS_AVAILABLE_MAC(14); @property (retain, nullable) NSString *ipaInstallPath; @property (readonly) BOOL isExplicitContents; @property BOOL isMDMProvided; @property unsigned long long itemIdentifier; @property (retain, nullable) NSString *kind; @property (retain, nullable) NSString *managedAppUUIDString; @property (readonly) BOOL needsSoftwareInstallOperation; @property (retain, nullable) NSURL *preflightPackageURL; @property (retain, nullable) NSString *productType; @property (readonly, nullable) NSString *purchaseDate; @property (getter=isRental) BOOL rental; @property (readonly, getter=isSample) BOOL sample; @property (retain, nullable) NSArray *> *sinfs; @property (readonly, nullable) NSString *sortArtist; @property (readonly, nullable) NSString *sortName; @property (retain, nullable) NSString *subtitle; @property (retain, nullable) NSURL *thumbnailImageURL; @property (retain, nullable) NSString *title; @property (retain, nullable) NSString *transactionIdentifier; @property (readonly, nullable) NSNumber *uncompressedSize; @property (retain, nullable) NSNumber *version; - (nullable id)_valueForFirstAvailableKey:(nullable id)key; - (nonnull instancetype)copyWithZone:(nullable struct _NSZone *)zone; - (nullable NSDictionary *)deltaPackages; // Unverified return type - (void)encodeWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)init; - (nonnull instancetype)initWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)initWithDictionary:(nullable NSDictionary *)dictionary; - (nonnull instancetype)initWithKind:(nullable NSString *)kind; - (nullable id)localServerInfo; - (void)setValue:(nullable id)value forMetadataKey:(nonnull NSString *)key; // Unverified key type @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/SSDownloadPhase.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface SSDownloadPhase : NSObject + (BOOL)supportsSecureCoding; @property (readonly) double estimatedSecondsRemaining; @property (readonly, nullable) SSOperationProgress *operationProgress; @property (readonly) long long phaseType; @property (readonly) float progressChangeRate; @property (readonly) long long progressUnits; @property (readonly) long long progressValue; @property (readonly) long long totalProgressValue; - (nonnull instancetype)copyWithZone:(nullable struct _NSZone *)zone; - (void)encodeWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)init; - (nonnull instancetype)initWithCoder:(nullable NSCoder *)coder; // Unverified coder type - (nonnull instancetype)initWithOperationProgress:(nullable SSOperationProgress *)progress; // Unverified progress type @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/SSDownloadStatus.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface SSDownloadStatus : NSObject + (BOOL)supportsSecureCoding; @property (readonly, nonatomic, nullable) SSDownloadPhase *activePhase; @property (nonatomic, getter=isCancelled) BOOL cancelled; @property (retain, nonatomic, nullable) NSError *error; @property (nonatomic, getter=isFailed) BOOL failed; @property (readonly, nonatomic, getter=isPausable) BOOL pausable; @property (nonatomic, getter=isPaused) BOOL paused; @property (readonly, nonatomic) float percentComplete; @property (readonly, nonatomic) float phasePercentComplete; @property (readonly, nonatomic) long long phaseTimeRemaining; @property BOOL waiting; - (nonnull instancetype)copyWithZone:(nullable struct _NSZone *)zone; - (void)encodeWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)initWithCoder:(nullable NSCoder *)coder; - (void)setOperationProgress:(nullable SSOperationProgress *)progress; // Unverified progress type @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/SSPurchase.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface SSPurchase : NSObject + (nonnull instancetype)purchaseWithBuyParameters:(nullable NSString *)buyParameters; // Unverified buyParameters type + (nonnull NSArray *> *)purchasesGroupedByAccountIdentifierWithPurchases:(nullable NSArray *)purchases; + (BOOL)supportsSecureCoding; @property (retain, nonatomic, nullable) NSNumber *accountIdentifier; @property (retain, nonatomic, nullable) NSString *appleID; @property (copy, nullable) UnknownBlock authFallbackHandler; // Unverified value type @property (copy, nonatomic, nullable) NSString *buyParameters; @property BOOL checkPreflightAterPurchase; @property (copy, nonatomic, nullable) SSDownloadMetadata *downloadMetadata; @property (retain, nullable) NSDictionary *dsidLessOptions; @property BOOL isCancelled; @property BOOL isDSIDLessPurchase; @property BOOL isRecoveryPurchase; @property BOOL isRedownload; @property BOOL isUpdate; @property BOOL isVPP; @property unsigned long long itemIdentifier; @property (retain, nonatomic, nullable) NSString *managedAppUUIDString; @property (readonly) BOOL needsAuthentication; @property (retain, nonatomic, nullable) NSString *parentalControls; @property (weak, nullable) ISOperation *purchaseOperation; @property (nonatomic) long long purchaseType; @property (retain, nonatomic, nullable) NSData *receiptData; @property (copy, nullable) NSDictionary *responseDialog; @property BOOL shouldBeInstalledAfterLogout; @property (readonly, nonatomic, nullable) NSString *sortableAccountIdentifier; @property (readonly, nonatomic, nonnull) NSString *uniqueIdentifier; - (nullable NSString *)_sortableAccountIdentifier; - (nonnull instancetype)copyWithZone:(nullable struct _NSZone *)zone; - (nonnull NSString *)description; - (void)encodeWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)initWithCoder:(nullable NSCoder *)coder; - (nonnull NSNumber *)productID; - (BOOL)purchaseDSIDMatchesPrimaryAccount; @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/SSPurchaseResponse.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @interface SSPurchaseResponse : NSObject { NSDictionary *_rawResponse; } + (BOOL)supportsSecureCoding; @property (retain, nullable) NSArray *downloads; @property (retain, nullable) NSDictionary *metrics; - (nonnull NSMutableArray *)_newDownloadsFromItems:(nullable NSArray *)items withDSID:(nullable NSNumber *)dsID; // Unverified items element generic types / dsID type - (void)encodeWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)initWithCoder:(nullable NSCoder *)coder; - (nonnull instancetype)initWithDictionary:(nullable NSDictionary *)dictionary userIdentifier:(nullable NSString *)userIdentifier; // Unverified dictionary generic types / userIdentifier type @end ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/StoreFoundation.h ================================================ // // Generated by https://github.com/blacktop/ipsw (Version: 3.1.660, BuildCommit: Homebrew) // // - LC_BUILD_VERSION: Platform: macOS, MinOS: 26.2, SDK: 26.2, Tool: ld (1230.3) // - LC_SOURCE_VERSION: 716.2.2.0.0 // @import Foundation; @class ISAuthenticationContext, ISAuthenticationResponse, ISOperation, ISStoreClient, SSDownloadAsset, SSOperationProgress; @protocol ISAccountStoreObserver, ISAssetService, ISDownloadService, ISInAppService, ISServiceRemoteObject, ISTransactionService, ISUIService, ISURLBagObserver; typedef void (^UnknownBlock)(); #import "ISStoreAccount.h" #import "ISAccountService-Protocol.h" #import "ISServiceProxy.h" #import "SSDownloadMetadata.h" #import "SSDownloadPhase.h" #import "SSDownloadStatus.h" #import "SSDownload.h" #import "SSPurchase.h" #import "SSPurchaseResponse.h" ================================================ FILE: Sources/PrivateFrameworks/include/StoreFoundation/module.modulemap ================================================ module StoreFoundation [system] [no_undeclared_includes] { requires macos, objc use Foundation link framework "StoreFoundation" umbrella header "StoreFoundation.h" export * } ================================================ FILE: Sources/mas/AppStore/AppStoreAction+download.swift ================================================ // // AppStoreAction+download.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // private import CommerceKit private import CoreServices private import Foundation private import ObjectiveC private import StoreFoundation extension AppStoreAction { // swiftlint:disable:this file_types_order @MainActor func app(withADAMID adamID: ADAMID, shouldCancel: @escaping @Sendable (String?, Bool) -> Bool) async throws { let purchase = SSPurchase( buyParameters: """ productType=C&price=0&pg=default&appExtVrsId=0&pricingParameters=\ \(self == .get ? "STDQ&macappinstalledconfirmed=1" : "STDRDL")&salableAdamId=\(adamID) """, ) // Possibly unnecessary… purchase.isRedownload = self != .get purchase.isUpdate = self == .update purchase.itemIdentifier = adamID let downloadMetadata = SSDownloadMetadata(kind: "software") downloadMetadata.itemIdentifier = adamID purchase.downloadMetadata = downloadMetadata let queue = CKDownloadQueue.shared() let observer = DownloadQueueObserver(for: self, of: adamID, shouldCancel: shouldCancel) let observerUUID = queue.add(observer) defer { queue.removeObserver(observerUUID) } try await withCheckedThrowingContinuation { continuation in observer.set(continuation: continuation) CKPurchaseController.shared().perform(purchase, withOptions: 0) { _, _, error, response in if let error { Task { await observer.resumeOnce { $0.resume(throwing: error) } } } else if response?.downloads?.isEmpty != false { Task { await observer.resumeOnce { continuation in continuation.resume(throwing: MASError.error("No downloads initiated for ADAM ID \(adamID)")) } } } } } } } private actor DownloadQueueObserver: CKDownloadQueueObserver { private let action: AppStoreAction private let adamID: ADAMID private let shouldCancel: @Sendable (String?, Bool) -> Bool private let downloadFolderURL: URL private nonisolated(unsafe) var continuation = CheckedContinuation?.none private var prevPhaseType = PhaseType.processing private var pkgHardLinkURL = URL?.none private var receiptHardLinkURL = URL?.none private var alreadyResumed = false init(for action: AppStoreAction, of adamID: ADAMID, shouldCancel: @escaping @Sendable (String?, Bool) -> Bool) { self.action = action self.adamID = adamID self.shouldCancel = shouldCancel downloadFolderURL = URL(filePath: "\(CKDownloadDirectory(nil))/\(adamID)", directoryHint: .isDirectory) } deinit { resumeOnce( alreadyResumed: alreadyResumed, pkgHardLinkURL: pkgHardLinkURL, receiptHardLinkURL: receiptHardLinkURL, ) { continuation in continuation // swiftformat:disable:next indent .resume(throwing: MASError.error("Observer deallocated before download completed for ADAM ID \(adamID)")) } } nonisolated func set(continuation: CheckedContinuation) { unsafe self.continuation = continuation } nonisolated func downloadQueue(_: CKDownloadQueue, changedWithAddition _: SSDownload) { // Do nothing } nonisolated func downloadQueue(_ queue: CKDownloadQueue, statusChangedFor download: SSDownload) { guard let snapshot = DownloadSnapshot(to: action, download), snapshot.adamID == adamID, !snapshot.isCancelled, !snapshot.isFailed else { return } guard !shouldCancel(snapshot.version, true) else { queue.cancelDownload(download, promptToConfirm: false, askToDelete: false) return } Task { await statusChanged(for: snapshot) } } nonisolated func downloadQueue(_: CKDownloadQueue, changedWithRemoval download: SSDownload) { guard let snapshot = DownloadSnapshot(to: action, download), snapshot.adamID == adamID else { return } Task { await removed(snapshot) } } func resumeOnce(performing action: (CheckedContinuation) -> Void) { resumeOnce( alreadyResumed: alreadyResumed, pkgHardLinkURL: pkgHardLinkURL, receiptHardLinkURL: receiptHardLinkURL, performing: action, ) alreadyResumed = true } private nonisolated func resumeOnce( alreadyResumed: Bool, pkgHardLinkURL: URL?, receiptHardLinkURL: URL?, performing action: (CheckedContinuation) -> Void, ) { guard !alreadyResumed else { return } guard let continuation = unsafe continuation else { MAS.printer.error("Failed to obtain download continuation for ADAM ID \(adamID)") return } action(continuation) deleteTempFolder(containing: pkgHardLinkURL, fileType: "pkg") deleteTempFolder(containing: receiptHardLinkURL, fileType: "receipt") } private func statusChanged(for snapshot: DownloadSnapshot) { // Refresh hard links to latest artifacts in the download directory do { let downloadFolderChildURLs = try FileManager.default.contentsOfDirectory( at: downloadFolderURL, includingPropertiesForKeys: [.contentModificationDateKey, .isRegularFileKey], ) do { pkgHardLinkURL = try hardLinkURL( to: try downloadFolderChildURLs.compactMap { url in guard url.pathExtension == "pkg" else { return nil as (url: URL, date: Date)? } let resourceValues = try url.resourceValues(forKeys: [.contentModificationDateKey, .isRegularFileKey]) return resourceValues.isRegularFile == true ? resourceValues.contentModificationDate.map { (url, $0) } : nil } .max { $0.date < $1.date }? .url, existing: pkgHardLinkURL, ) } catch { MAS.printer.warning("Failed to link pkg for", snapshot.appNameAndVersion, error: error) } do { receiptHardLinkURL = try hardLinkURL( to: downloadFolderChildURLs.first { $0.lastPathComponent == "receipt" }, existing: receiptHardLinkURL, ) } catch { MAS.printer.warning("Failed to link receipt for", snapshot.appNameAndVersion, error: error) } } catch { MAS.printer.warning( "Failed to read contents of download folder", downloadFolderURL.filePath.quoted, "for", snapshot.appNameAndVersion, error: error, ) } switch snapshot.activePhaseType { case prevPhaseType: break case .downloading where prevPhaseType == .processing, .downloaded where prevPhaseType == .downloading, .performing: MAS.printer.clearCurrentLine(of: .standardOutput) MAS.printer.notice(snapshot.activePhaseType, snapshot.appNameAndVersion) default: break } if FileHandle.standardOutput.isTerminal, snapshot.phasePercentComplete != 0 || snapshot.activePhaseType != .processing { // Output the progress bar iff connected to a terminal let totalLength = 60 let completedLength = Int(snapshot.phasePercentComplete * Float(totalLength)) MAS.printer.clearCurrentLine(of: .standardOutput) MAS.printer.info( String(repeating: "#", count: completedLength), String(repeating: "-", count: totalLength - completedLength), " ", UInt64((snapshot.phasePercentComplete * 100).rounded()), "% ", snapshot.activePhaseType.performed, separator: "", terminator: "", ) } prevPhaseType = snapshot.activePhaseType } private func removed(_ snapshot: DownloadSnapshot) async { MAS.printer.clearCurrentLine(of: .standardOutput) do { let appFolderURL: URL? if let error = snapshot.error { guard error is Ignorable else { throw error } MAS.printer.notice(PhaseType.downloaded, snapshot.appNameAndVersion) MAS.printer.notice(action.performing.capitalizingFirstCharacter, snapshot.appNameAndVersion) MAS.printer.info( String(describing: action).capitalizingFirstCharacter, "progress cannot be displayed", terminator: "", ) appFolderURL = try await install(appNameAndVersion: snapshot.appNameAndVersion) MAS.printer.clearCurrentLine(of: .standardOutput) } else { guard !snapshot.isFailed else { throw MASError.error("Failed to download \(snapshot.appNameAndVersion)") } guard !shouldCancel(snapshot.version, false) else { resumeOnce { $0.resume() } return } guard !snapshot.isCancelled else { throw MASError.error("Download cancelled for \(snapshot.appNameAndVersion)") } appFolderURL = snapshot.appFolderPath.map { .init(filePath: $0, directoryHint: .isDirectory) } } MAS.printer.notice( [action.performed.capitalizingFirstCharacter, snapshot.appNameAndVersion] + (appFolderURL.map { ["in", $0.filePath] } ?? []), // swiftformat:disable:this indent ) if let appFolderURL { let fileManager = FileManager.default if try applicationsFolderURLs.contains( where: { applicationsFolderURL in var relationship = FileManager.URLRelationship.other try unsafe fileManager.getRelationship( &relationship, ofDirectoryAt: applicationsFolderURL, toItemAt: appFolderURL, ) return relationship == .contains }, ) { let appFolderPath = appFolderURL.filePath let installedApps = try await installedApps(withADAMID: snapshot.adamID).filter { $0.path != appFolderPath } if !installedApps.isEmpty { MAS.printer.warning( "Multiple installations of ", snapshot.name ?? "unknown app", " exist in the applications folders\n\n", action.performed.capitalizingFirstCharacter, ":\n", appFolderPath, "\n\nOthers:\n", installedApps.map(\.path).sorted(using: .localizedStandard).joined(separator: "\n"), separator: "", ) } } else { MAS.printer.warning( action.performed.capitalizingFirstCharacter, snapshot.appNameAndVersion, "outside of the applications folders, in", appFolderURL.filePath, ) } } resumeOnce { $0.resume() } } catch { resumeOnce { $0.resume(throwing: error) } } } private func hardLinkURL(to url: URL?, existing existingHardLinkURL: URL?) throws -> URL? { guard let url, try !url.linksToSameInode(as: existingHardLinkURL) else { return existingHardLinkURL } let fileManager = FileManager.default let hardLinkURL = try fileManager.url( for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: url, create: true, ) .appending(path: "\(adamID)-\(url.lastPathComponent)", directoryHint: .notDirectory) try fileManager.linkItem(at: url, to: hardLinkURL) return hardLinkURL } private func install(appNameAndVersion: String) async throws -> URL { guard let pkgHardLinkPath = pkgHardLinkURL?.filePath else { throw MASError.error("Failed to find pkg to \(action) \(appNameAndVersion)") } guard let receiptHardLinkURL else { throw MASError.error("Failed to find receipt to import for \(appNameAndVersion)") } let (_, standardErrorString) = try await run( "/usr/sbin/installer", "-dumplog", "-pkg", pkgHardLinkPath, "-target", "/", errorMessage: "Failed to \(action) \(appNameAndVersion) from \(pkgHardLinkPath)", ) { process in try run(asEffectiveUID: 0, andEffectiveGID: 0) { try process.run() } } guard let appFolderURLSubstring = standardErrorString .matches(of: unsafe appFolderURLRegex) // swiftformat:disable indent .compactMap(\.1) .min(by: { $0.count < $1.count }) else { // swiftformat:enable indent throw MASError.error( "Failed to find app folder URL in installer output for \(appNameAndVersion)", error: standardErrorString, ) } guard let appFolderURL = URL(string: .init(appFolderURLSubstring)) else { throw MASError.error( "Failed to parse app folder URL for \(appNameAndVersion) from \(appFolderURLSubstring)", error: standardErrorString, ) } let receiptURL = appFolderURL.appending(path: "Contents/_MASReceipt/receipt", directoryHint: .notDirectory) do { let fileManager = FileManager.default try run(asEffectiveUID: 0, andEffectiveGID: 0) { if fileManager.fileExists(atPath: receiptURL.filePath) { try fileManager.removeItem(at: receiptURL) } else { try fileManager.createDirectory( at: receiptURL.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: [.ownerAccountID: 0, .groupOwnerAccountID: 0, .posixPermissions: 0o755], ) } try fileManager.copyItem(at: receiptHardLinkURL, to: receiptURL) try fileManager.setAttributes( [.ownerAccountID: 0, .groupOwnerAccountID: 0, .posixPermissions: 0o755], ofItemAtPath: receiptURL.filePath, ) } } catch { throw MASError.error( """ Failed to copy receipt for \(appNameAndVersion) from \(receiptHardLinkURL.filePath.quoted) to\ \(receiptURL.filePath.quoted) """, error: error, ) } _ = try await run( "/usr/bin/mdimport", appFolderURL.filePath, errorMessage: "Failed to \(action) \(appNameAndVersion) from \(pkgHardLinkPath)", ) LSRegisterURL(appFolderURL as NSURL, true) // swiftlint:disable:this legacy_objc_type return appFolderURL } } private struct DownloadSnapshot { // swiftlint:disable:this one_declaration_per_file let adamID: ADAMID let version: String? let name: String? let appNameAndVersion: String let activePhaseType: PhaseType let phasePercentComplete: Float let appFolderPath: String? let isCancelled: Bool let isFailed: Bool let error: (any Error)? init?(to action: AppStoreAction, _ download: SSDownload) { guard let metadata = download.metadata, let status = download.status else { return nil } adamID = metadata.itemIdentifier name = metadata.title version = metadata.bundleVersion appNameAndVersion = "\(metadata.title ?? "unknown app") (\(version ?? "unknown version"))" activePhaseType = PhaseType(action, rawValue: status.activePhase?.phaseType) phasePercentComplete = status.phasePercentComplete appFolderPath = download.installPath isCancelled = status.isCancelled isFailed = status.isFailed error = status.error.map { $0 as NSError }.map { error in error.domain == "PKInstallErrorDomain" && error.code == 201 ? Ignorable.installerWorkaround : error as any Error } } } private enum Ignorable: Error { // swiftlint:disable:this one_declaration_per_file case installerWorkaround } private enum PhaseType: Equatable { // swiftlint:disable:this one_declaration_per_file case processing // swiftlint:disable:this sorted_enum_cases case downloading case downloaded // swiftlint:disable:this sorted_enum_cases case performing(AppStoreAction) // swiftlint:disable:this sorted_enum_cases var performed: String { switch self { case .processing: "processed" case // swiftformat:disable:this sortSwitchCases .downloading, .downloaded: "downloaded" case let .performing(action): action.performed } } init(_ action: AppStoreAction, rawValue: Int64?) { self = switch rawValue { case 0: .downloading case 1: .performing(action) case 5: .downloaded default: .processing } } } extension PhaseType: CustomStringConvertible { var description: String { switch self { case .processing: "Processing" case .downloading: "Downloading" case .downloaded: "Downloaded" case let .performing(action): action.performing } } } private extension String { var capitalizingFirstCharacter: Self { prefix(1).capitalized + dropFirst() } } private extension URL { func linksToSameInode(as url: URL?) throws -> Bool { guard let url, url.isFileURL, isFileURL else { return false } guard let fileID1 = try resourceValues(forKeys: [.fileResourceIdentifierKey]).fileResourceIdentifier else { throw MASError.error("Failed to get file resource identifier for \(filePath)") } guard let fileID2 = try url.resourceValues(forKeys: [.fileResourceIdentifierKey]).fileResourceIdentifier else { throw MASError.error("Failed to get file resource identifier for \(url.filePath)") } return fileID1.isEqual(fileID2) } } private func deleteTempFolder(containing url: URL?, fileType: String) { url.map { url in do { try FileManager.default.removeItem(at: url.deletingLastPathComponent()) } catch { MAS.printer.warning("Failed to delete temp folder containing", fileType, url.filePath, error: error) } } } private nonisolated(unsafe) let appFolderURLRegex = /PackageKit: Registered bundle (\S+) for uid 0/ ================================================ FILE: Sources/mas/AppStore/AppStoreAction.swift ================================================ // // AppStoreAction.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // private import ArgumentParser private import Darwin private import OrderedCollections private import StoreFoundation enum AppStoreAction { case get case install case update var performed: String { switch self { case .get: "got" case .install: "installed" case .update: "updated" } } var performing: String { switch self { case .get: "getting" case .install: "installing" case .update: "updating" } } func apps( withAppIDs appIDs: [AppID], force: Bool, installedApps: [InstalledApp], lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp, ) async throws { try await apps( withADAMIDs: await appIDs.lookupCatalogApps(using: lookupAppFromAppID).map(\.adamID), force: force, installedApps: installedApps, ) } func apps(withADAMIDs adamIDs: [ADAMID], force: Bool, installedApps: [InstalledApp]) async throws { try await apps( withADAMIDs: adamIDs.filter { adamID in if !force, let installedApp = installedApps.first(where: { $0.adamID == adamID }) { MAS.printer.warning("Already ", performed, " ", installedApp.name, " (", adamID, ")", separator: "") return false } return true }, ) } func apps(withADAMIDs adamIDs: [ADAMID]) async throws { guard !adamIDs.isEmpty else { return } let adamIDs = OrderedSet(adamIDs) guard getuid() == 0 else { try sudo(MAS._commandName, args: [String(describing: self), "--force"] + adamIDs.map(String.init(describing:))) return } await adamIDs.forEach(attemptTo: "\(self) app for ADAM ID") { adamID in try await app(withADAMID: adamID) { _, _ in false } } } } typealias AppStore = AppStoreAction ================================================ FILE: Sources/mas/AppStore/Region.swift ================================================ // // Region.swift // mas // // Copyright © 2024 mas-cli. All rights reserved. // private import Foundation typealias Region = String private extension Region { var appStoreRegion: Self { switch self { // swiftlint:disable switch_case_on_newline case "AD": "ES" // Andorra > Spain case "AQ": "NO" // Antarctica > Norway case "AS": "US" // American Samoa > United States case "AW": "NL" // Aruba > Netherlands case "AX": "FI" // Åland Islands > Finland case "BD": "IN" // Bangladesh > India case "BI": "KE" // Burundi > Kenya case "BL": "FR" // St. Barthélemy > France case "BQ": "NL" // Bonaire, Sint Eustatius and Saba > Netherlands case "BV": "NO" // Bouvet Island > Norway case "CC": "AU" // Cocos (Keeling) Islands > Australia case "CF": "FR" // Central African Republic > France case "CK": "NZ" // Cook Islands > New Zealand case "CU": "US" // Cuba > United States case "CW": "NL" // Curaçao > Netherlands case "CX": "AU" // Christmas Island > Australia case "DJ": "FR" // Djibouti > France case "EH": "MA" // Western Sahara > Morocco case "ER": "KE" // Eritrea > Kenya case "ET": "KE" // Ethiopia > Kenya case "FK": "GB" // Falkland Islands > United Kingdom case "FO": "DK" // Faroe Islands > Denmark case "GF": "FR" // French Guiana > France case "GG": "GB" // Guernsey > United Kingdom case "GI": "GB" // Gibraltar > United Kingdom case "GL": "DK" // Greenland > Denmark case "GN": "FR" // Guinea > France case "GP": "FR" // Guadeloupe > France case "GQ": "FR" // Equatorial Guinea > France case "GS": "GB" // South Georgia and the South Sandwich Islands > United Kingdom case "GU": "US" // Guam > United States case "HM": "AU" // Heard Island and McDonald Islands > Australia case "HT": "US" // Haiti > United States case "IC": "ES" // Canary Islands > Spain case "IM": "GB" // Isle of Man > United Kingdom case "IO": "GB" // British Indian Ocean Territory > United Kingdom case "IR": "TR" // Iran > Türkiye case "JE": "GB" // Jersey > United Kingdom case "KI": "AU" // Kiribati > Australia case "KM": "FR" // Comoros > France case "KP": "CN" // Korea, Democratic People's Republic of > China case "LI": "CH" // Liechtenstein > Switzerland case "LS": "ZA" // Lesotho > South Africa case "MC": "FR" // Monaco > France case "MF": "FR" // St. Martin > France case "MH": "US" // Marshall Islands > United States case "MP": "US" // Northern Mariana Islands > United States case "MQ": "FR" // Martinique > France case "NC": "FR" // New Caledonia > France case "NF": "AU" // Norfolk Island > Australia case "NU": "NZ" // Niue > New Zealand case "PF": "FR" // French Polynesia > France case "PM": "FR" // St. Pierre and Miquelon > France case "PN": "NZ" // Pitcairn Islands > New Zealand case "PR": "US" // Puerto Rico > United States case "PS": "IL" // Palestine > Israel case "RE": "FR" // Réunion > France case "SD": "EG" // Sudan > Egypt case "SH": "GB" // St. Helena, Ascension and Tristan da Cunha > United Kingdom case "SJ": "NO" // Svalbard and Jan Mayen > Norway case "SM": "IT" // San Marino > Italy case "SO": "KE" // Somalia > Kenya case "SS": "KE" // South Sudan > Kenya case "SX": "NL" // Sint Maarten > Netherlands case "SY": "TR" // Syria > Türkiye case "TF": "FR" // French Southern Territories > France case "TG": "FR" // Togo > France case "TK": "NZ" // Tokelau > New Zealand case "TL": "ID" // Timor-Leste > Indonesia case "TV": "AU" // Tuvalu > Australia case "UM": "US" // United States Minor Outlying Islands > United States case "VA": "IT" // Vatican City State > Italy case "VI": "US" // Virgin Islands of the United States > United States case "WF": "FR" // Wallis and Futuna > France case "WS": "AU" // Samoa > Australia case "YT": "FR" // Mayotte > France default: self // swiftlint:enable switch_case_on_newline } } } var appStoreRegion: Region { macRegion.appStoreRegion } var macRegion: Region { Locale.current.region?.identifier ?? "US" } ================================================ FILE: Sources/mas/Commands/Config.swift ================================================ // // Config.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import ArgumentParser private import Darwin private import Foundation extension MAS { /// Outputs mas config & related system info. struct Config: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Output mas config & related system info", ) func run() { printer.info( """ mas ▁▁▁▁ \(version) slice ▁▁ \(runningSliceArchitecture) slices ▁ \(supportedSliceArchitectures.joined(separator: " ")) dist ▁▁▁ \(distribution) origin ▁ \(gitOrigin) rev ▁▁▁▁ \(gitRevision) swift ▁▁ \(swiftVersion) driver ▁ \(swiftDriverVersion) store ▁▁ \(appStoreRegion) region ▁ \(macRegion) macos ▁▁ \(macOSVersion) mac ▁▁▁▁ \(configStringValue("hw.product")) cpu ▁▁▁▁ \(configStringValue("machdep.cpu.brand_string")) arch ▁▁▁ \(configStringValue("hw.machine")) """, ) } } } private var runningSliceArchitecture: String { var info = utsname() return unsafe uname(&info) == 0 ? withUnsafePointer(to: &info.machine) { pointer in // swiftformat:disable indent unsafe pointer.withMemoryRebound( to: CChar.self, capacity: unsafe MemoryLayout.size(ofValue: unsafe pointer), unsafe String.init(cString:), ) } : unknown } // swiftformat:enable indent private var supportedSliceArchitectures: [String] { Bundle.main.executableArchitectures.map { archIDs in archIDs.map { archID in guard let arch = Int(exactly: archID) else { return "unknown_\(archID)" } return switch arch { case NSBundleExecutableArchitectureARM64: "arm64" case NSBundleExecutableArchitectureI386: "i386" case NSBundleExecutableArchitecturePPC: "ppc" case NSBundleExecutableArchitecturePPC64: "ppc64" case NSBundleExecutableArchitectureX86_64: "x86_64" default: "unknown_0x\(String(arch, radix: 16))" } } } ?? [] // swiftformat:disable:this indent } private var macOSVersion: Substring { ProcessInfo.processInfo.operatingSystemVersionString.dropFirst(8).replacing("Build ", with: "", maxReplacements: 1) } private func configStringValue(_ name: String) -> String { var size = 0 guard unsafe sysctlbyname(name, nil, &size, nil, 0) == 0 else { unsafe perror(sysCtlByName) return unknown } var buffer = [CChar](repeating: 0, count: size) guard unsafe sysctlbyname(name, &buffer, &size, nil, 0) == 0 else { unsafe perror(sysCtlByName) return unknown } return unsafe String(cString: &buffer) } private let unknown = "unknown" private let sysCtlByName = "sysctlbyname" ================================================ FILE: Sources/mas/Commands/Get.swift ================================================ // // Get.swift // mas // // Copyright © 2026 mas-cli. All rights reserved. // internal import ArgumentParser extension MAS { /// Gets & installs free apps from the App Store. struct Get: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Get & install free apps from the App Store", discussion: requiresRootPrivilegesMessage(), aliases: ["purchase"], ) @OptionGroup private var forceOptionGroup: ForceOptionGroup @OptionGroup private var catalogAppIDsOptionGroup: CatalogAppIDsOptionGroup func run() async throws { try await AppStore.get.apps( withAppIDs: catalogAppIDsOptionGroup.appIDs, force: forceOptionGroup.force, installedApps: try await installedApps, lookupAppFromAppID: lookup(appID:), ) } } } ================================================ FILE: Sources/mas/Commands/Home.swift ================================================ // // Home.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // internal import ArgumentParser private import Foundation extension MAS { /// Opens App Store app pages in the default web browser. /// /// Uses the iTunes Lookup API: /// /// https://performance-partners.apple.com/search-api struct Home: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Open App Store app pages in the default web browser", ) @OptionGroup private var catalogAppIDsOptionGroup: CatalogAppIDsOptionGroup func run() async { await run(lookupAppFromAppID: lookup(appID:)) } private func run(lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp) async { await run(catalogApps: await catalogAppIDsOptionGroup.appIDs.lookupCatalogApps(using: lookupAppFromAppID)) } func run(catalogApps: [CatalogApp]) async { // swiftformat:disable:this organizeDeclarations await run(appStorePageURLStrings: catalogApps.map(\.appStorePageURLString)) } private func run(appStorePageURLStrings: [String]) async { // swiftformat:disable:this organizeDeclarations await appStorePageURLStrings.forEach(attemptTo: "open") { appStorePageURLString in guard let url = URL(string: appStorePageURLString) else { throw MASError.unparsableURL(appStorePageURLString) } try await url.open() } } } } ================================================ FILE: Sources/mas/Commands/Install.swift ================================================ // // Install.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // internal import ArgumentParser extension MAS { /// Installs previously gotten apps from the App Store. struct Install: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Install previously gotten apps from the App Store", discussion: requiresRootPrivilegesMessage(), ) @OptionGroup private var forceOptionGroup: ForceOptionGroup @OptionGroup private var catalogAppIDsOptionGroup: CatalogAppIDsOptionGroup func run() async throws { try await AppStore.install.apps( withAppIDs: catalogAppIDsOptionGroup.appIDs, force: forceOptionGroup.force, installedApps: try await installedApps, lookupAppFromAppID: lookup(appID:), ) } } } ================================================ FILE: Sources/mas/Commands/List.swift ================================================ // // List.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // internal import ArgumentParser private import Foundation extension MAS { /// Lists all apps installed from the App Store. struct List: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "List apps installed from the App Store", ) @OptionGroup private var installedAppIDsOptionGroup: InstalledAppIDsOptionGroup func run() async throws { run(installedApps: try await installedApps) } func run(installedApps: [InstalledApp]) { let installedApps = installedApps.filter(for: installedAppIDsOptionGroup.appIDs) guard let maxADAMIDLength = installedApps.map({ String(describing: $0.adamID).count }).max(), let maxNameLength = installedApps.map(\.name.count).max() else { printer.warning( """ No installed apps found If this is unexpected, any of the following command lines should fix things by reindexing apps in Spotlight\ (which might take some time): # Individual apps (if you know exactly what apps were incorrectly omitted): mdimport /Applications/Example.app # All apps ( is the volume optionally selected for large apps): mdimport /Applications /Volumes//Applications # All file system volumes (if neither aforementioned command solved the issue): sudo mdutil -Eai on """, ) return } let format = "%\(maxADAMIDLength)lu %@ (%@)" printer.info( installedApps.map { installedApp in String( format: format, installedApp.adamID, installedApp.name.padding(toLength: maxNameLength, withPad: " ", startingAt: 0), installedApp.version, ) } .joined(separator: "\n"), ) } } } ================================================ FILE: Sources/mas/Commands/Lookup.swift ================================================ // // Lookup.swift // mas // // Copyright © 2016 mas-cli. All rights reserved. // internal import ArgumentParser private import Foundation extension MAS { /// Outputs app information from the App Store. /// /// Uses the iTunes Lookup API: /// /// https://performance-partners.apple.com/search-api struct Lookup: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Output app information from the App Store", aliases: ["info"], ) @OptionGroup private var catalogAppIDsOptionGroup: CatalogAppIDsOptionGroup func run() async { await run(lookupAppFromAppID: lookup(appID:)) } private func run(lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp) async { run(catalogApps: await catalogAppIDsOptionGroup.appIDs.lookupCatalogApps(using: lookupAppFromAppID)) } func run(catalogApps: [CatalogApp]) { // swiftformat:disable:this organizeDeclarations printer.info( catalogApps.map { catalogApp in """ \(catalogApp.name) \(catalogApp.version) [\(catalogApp.displayPrice)] By: \(catalogApp.sellerName) Released: \(catalogApp.releaseDate.isoCalendarDate) Minimum OS: \(catalogApp.minimumOSVersion) Size: \(catalogApp.fileSizeBytes.humanReadableSize) From: \(catalogApp.appStorePageURLString) """ } .joined(separator: "\n"), terminator: "", ) } } } private extension String { var humanReadableSize: Self { Int64(self).map { $0.formatted(.byteCount(style: .file, allowedUnits: .mb, spellsOutZero: false)) } ?? self } var isoCalendarDate: Self { (try? Date(self, strategy: .iso8601).formatted(Date.ISO8601FormatStyle(timeZone: .current).year().month().day())) ?? self // swiftformat:disable:this indent } } ================================================ FILE: Sources/mas/Commands/Lucky.swift ================================================ // // Lucky.swift // mas // // Copyright © 2017 mas-cli. All rights reserved. // internal import ArgumentParser extension MAS { /// Installs the first app returned from searching the App Store (app must /// have been previously gotten). /// /// Uses the iTunes Search API: /// /// https://performance-partners.apple.com/search-api struct Lucky: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Install the first app returned from searching the App Store", discussion: // swiftformat:disable:next indent "App will install only if it has already been gotten\n\n\(requiresRootPrivilegesMessage(to: "install"))", ) @OptionGroup private var forceOptionGroup: ForceOptionGroup @OptionGroup private var searchTermOptionGroup: SearchTermOptionGroup func run() async throws { try await run(installedApps: try await installedApps, searchForAppsMatchingSearchTerm: search(for:)) } private func run( installedApps: [InstalledApp], searchForAppsMatchingSearchTerm: (String) async throws -> [CatalogApp], ) async throws { let searchTerm = searchTermOptionGroup.searchTerm guard let adamID = try await searchForAppsMatchingSearchTerm(searchTerm).first?.adamID else { throw MASError.noCatalogAppsFound(for: searchTerm) } try await run(installedApps: installedApps, adamID: adamID) } private func run(installedApps: [InstalledApp], adamID: ADAMID) async throws { try await AppStore.install.apps( withADAMIDs: [adamID], force: forceOptionGroup.force, installedApps: installedApps, ) } } } ================================================ FILE: Sources/mas/Commands/MAS.swift ================================================ // // MAS.swift // mas // // Copyright © 2021 mas-cli. All rights reserved. // internal import ArgumentParser internal import Foundation @main struct MAS: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Mac App Store command-line interface", version: Self.version, subcommands: [ Config.self, Get.self, Home.self, Install.self, List.self, Lookup.self, Lucky.self, Open.self, Outdated.self, Reset.self, Search.self, Seller.self, SignOut.self, Uninstall.self, Update.self, Version.self, ], ) static let printer = Printer() static var _errorPrefix: String { // swiftlint:disable:this identifier_name "\(errorPrefix.formatted(with: errorFormat, for: FileHandle.standardError)) " } private static func main() async { // swiftlint:disable:this unused_declaration await main(nil) } private static func main(_ arguments: [String]?) async { // swiftlint:disable:this discouraged_optional_collection do { let command = try parseAsRoot(arguments) if let command = cast(command, as: (any AsyncParsableCommand & Sendable).self) { try await main(command) } else { try main(command) } let errorCount = printer.errorCount if errorCount > 0 { throw ExitCode(errorCount >= UInt64(Int32.max) ? .max : .init(errorCount)) } } catch { exit(withError: error) } } } extension MAS { static func main(_ command: some ParsableCommand) throws { try main(command) { command in var command = command try command.run() } } static func main(_ command: some AsyncParsableCommand & Sendable) async throws { try await main(command) { command in var command = command try await command.run() } } static func main(_ command: Command, _ body: (Command) throws -> Void) throws { do { try ProcessInfo.processInfo.runAsSudoEffectiveUserAndSudoEffectiveGroupIfRootEffectiveUser { try body(command) } } catch { printer.error(error: try error.failure) } } static func main(_ command: Command, _ body: (Command) async throws -> Void) async throws { // swiftformat:disable:this indent do { try await ProcessInfo.processInfo.runAsSudoEffectiveUserAndSudoEffectiveGroupIfRootEffectiveUser { try await body(command) } } catch { printer.error(error: try error.failure) } } } private extension Error { var failure: Self { get throws { guard !MAS.exitCode(for: self).isSuccess else { throw self } return self } } } extension ParsableCommand { static func requiresRootPrivilegesMessage(to action: String = String(describing: Self.self).lowercased()) -> String { "Requires root privileges to \(action) apps" } } private func cast(_ instance: Any, as _: T.Type) -> T? { instance as? T } private let applicationsFolderPath = "/Applications" private let applicationsFolderURL = URL(filePath: applicationsFolderPath, directoryHint: .isDirectory) let applicationsFolderURLs = UserDefaults(suiteName: "com.apple.appstored")? .dictionary(forKey: "PreferredVolume")?["name"] // swiftformat:disable indent .map { [applicationsFolderURL, URL(filePath: "/Volumes/\($0)\(applicationsFolderPath)", directoryHint: .isDirectory)] } ?? [applicationsFolderURL] // swiftformat:enable indent ================================================ FILE: Sources/mas/Commands/Open.swift ================================================ // // Open.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // private import AppKit internal import ArgumentParser private import Foundation private import ObjectiveC extension MAS { /// Opens app page in 'App Store.app'. /// /// Uses the iTunes Lookup API: /// /// https://performance-partners.apple.com/search-api struct Open: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Open app page in 'App Store.app'", ) @OptionGroup private var forceBundleIDOptionGroup: ForceBundleIDOptionGroup @Argument(help: .init("App ID", valueName: "app-id")) private var appIDString: String? func run() async throws { try await run(lookupAppFromAppID: lookup(appID:)) } private func run(lookupAppFromAppID: (AppID) async throws -> CatalogApp) async throws { try await run(appStorePageURLString: appStorePageURLString(lookupAppFromAppID: lookupAppFromAppID)) } private func run(appStorePageURLString: String?) async throws { guard let appStorePageURLString else { // If no App Store Page URL was given, just open the MAS GUI app try await openMacAppStore() return } try await openMacAppStorePage(forAppStorePageURLString: appStorePageURLString) } private func appStorePageURLString(lookupAppFromAppID: (AppID) async throws -> CatalogApp) async throws -> String? { guard let appIDString else { return nil } return try await lookupAppFromAppID( AppID(from: appIDString, forceBundleID: forceBundleIDOptionGroup.forceBundleID), ) .appStorePageURLString } } } private func openMacAppStore() async throws { guard let macAppStoreSchemeURL = URL(string: "macappstore:") else { throw MASError.error("Failed to create URL from macappstore scheme") } let workspace = NSWorkspace.shared guard let appURL = workspace.urlForApplication(toOpen: macAppStoreSchemeURL) else { throw MASError.error("Failed to find app to open macappstore URLs") } try await workspace.openApplication(at: appURL, configuration: .init()) } private func openMacAppStorePage(forAppStorePageURLString appStorePageURLString: String) async throws { guard var urlComponents = URLComponents(string: appStorePageURLString) else { throw MASError.unparsableURL(appStorePageURLString) } urlComponents.scheme = masScheme guard let url = urlComponents.url else { throw MASError.unparsableURL(String(describing: urlComponents)) } try await url.open() } private let masScheme = "macappstore" ================================================ FILE: Sources/mas/Commands/OptionGroups/CatalogAppIDsOptionGroup.swift ================================================ // // CatalogAppIDsOptionGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import ArgumentParser struct CatalogAppIDsOptionGroup: ParsableArguments { @OptionGroup private var forceBundleIDOptionGroup: ForceBundleIDOptionGroup @Argument(help: .init("App ID", valueName: "app-id")) private var appIDStrings: [String] var appIDs: [AppID] { appIDStrings.map { .init(from: $0, forceBundleID: forceBundleIDOptionGroup.forceBundleID) } } } ================================================ FILE: Sources/mas/Commands/OptionGroups/ForceBundleIDOptionGroup.swift ================================================ // // ForceBundleIDOptionGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import ArgumentParser struct ForceBundleIDOptionGroup: ParsableArguments { @Flag(name: .customLong("bundle"), help: "Process all app IDs as bundle IDs") var forceBundleID = false } ================================================ FILE: Sources/mas/Commands/OptionGroups/ForceOptionGroup.swift ================================================ // // ForceOptionGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import ArgumentParser struct ForceOptionGroup: ParsableArguments { @Flag(help: "Force reinstall") var force = false } ================================================ FILE: Sources/mas/Commands/OptionGroups/InstalledAppIDsOptionGroup.swift ================================================ // // InstalledAppIDsOptionGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import ArgumentParser struct InstalledAppIDsOptionGroup: ParsableArguments { @OptionGroup private var forceBundleIDOptionGroup: ForceBundleIDOptionGroup @Argument(help: .init("App ID", valueName: "app-id")) private var appIDStrings = [String]() var appIDs: [AppID] { appIDStrings.map { .init(from: $0, forceBundleID: forceBundleIDOptionGroup.forceBundleID) } } } ================================================ FILE: Sources/mas/Commands/OptionGroups/OutdatedAccuracy.swift ================================================ // // OutdatedAccuracy.swift // mas // // Copyright © 2026 mas-cli. All rights reserved. // internal import ArgumentParser enum OutdatedAccuracy: String, EnumerableFlag { case accurate case inaccurate static func help(for outdatedAccuracy: Self) -> ArgumentHelp? { switch outdatedAccuracy { case .accurate: """ Use accurate, slower logic that starts then cancels a download for each queried app, which can exceed download\ limits & which will open dialogs for undownloadable apps """ case .inaccurate: "Use inaccurate, faster logic that avoids dialogs" } } } ================================================ FILE: Sources/mas/Commands/OptionGroups/OutdatedAppOptionGroup.swift ================================================ // // OutdatedAppOptionGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import ArgumentParser struct OutdatedAppOptionGroup: ParsableArguments { @Flag var accuracy = OutdatedAccuracy.inaccurate @Flag( name: .customLong("check-min-os"), inversion: .prefixedNo, help: "Check that macOS is new enough to install the latest app version", ) var shouldCheckMinimumOSVersion = true } ================================================ FILE: Sources/mas/Commands/OptionGroups/SearchTermOptionGroup.swift ================================================ // // SearchTermOptionGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import ArgumentParser struct SearchTermOptionGroup: ParsableArguments { @Argument(help: .init("Search terms are concatenated into a single search", valueName: "search-term")) private var searchTermElements: [String] var searchTerm: String { searchTermElements.joined(separator: " ") } } ================================================ FILE: Sources/mas/Commands/OptionGroups/VerboseOptionGroup.swift ================================================ // // VerboseOptionGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import ArgumentParser struct VerboseOptionGroup: ParsableArguments { @Flag(help: "Output warnings about app IDs unknown to the App Store") var verbose = false } ================================================ FILE: Sources/mas/Commands/Outdated.swift ================================================ // // Outdated.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // internal import ArgumentParser private import Foundation extension MAS { /// Outputs a list of installed apps which have updates available to be /// installed from the App Store. struct Outdated: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "List pending app updates from the App Store", ) @OptionGroup private var outdatedAppOptionGroup: OutdatedAppOptionGroup @OptionGroup private var verboseOptionGroup: VerboseOptionGroup @OptionGroup private var installedAppIDsOptionGroup: InstalledAppIDsOptionGroup func run() async throws { await run(installedApps: try await installedApps.filter(!\.isTestFlight), lookupAppFromAppID: lookup(appID:)) } private func run( installedApps: [InstalledApp], lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp, ) async { run( outdatedApps: await installedApps.outdatedApps( filterFor: installedAppIDsOptionGroup.appIDs, lookupAppFromAppID: lookupAppFromAppID, accuracy: outdatedAppOptionGroup.accuracy, shouldCheckMinimumOSVersion: outdatedAppOptionGroup.shouldCheckMinimumOSVersion, shouldWarnIfUnknownApp: verboseOptionGroup.verbose, ), ) } private func run(outdatedApps: [OutdatedApp]) { guard let maxADAMIDLength = outdatedApps.map({ String(describing: $0.installedApp.adamID).count }).max(), let maxNameLength = outdatedApps.map(\.installedApp.name.count).max(), let maxVersionLength = outdatedApps.map(\.installedApp.version.count).max() else { return } let format = "%\(maxADAMIDLength)lu %@ (%@ -> %@)" printer.info( outdatedApps.map { installedApp, newVersion in String( format: format, installedApp.adamID, installedApp.name.padding(toLength: maxNameLength, withPad: " ", startingAt: 0), installedApp.version.padding(toLength: maxVersionLength, withPad: " ", startingAt: 0), newVersion, ) } .joined(separator: "\n"), ) } } } ================================================ FILE: Sources/mas/Commands/Reset.swift ================================================ // // Reset.swift // mas // // Copyright © 2016 mas-cli. All rights reserved. // private import AppKit internal import ArgumentParser private import CommerceKit private import Darwin private import Foundation extension MAS { /// Mimics the "Reset Application" command in the App Store debug menu, which /// performs the following steps: /// /// - `killall Dock` /// - `killall storeagent` (`storeagent` no longer exists) /// - deletes the `com.apple.appstore` download folder /// - clears cookies (appears to be a no-op) /// /// As `storeagent` no longer exists, terminates all processes known to be /// associated with the App Store. struct Reset: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Reset App Store processes & clear cached App Store downloads", ) func run() { for bundleID in ["com.apple.dock", "com.apple.storeuid"] { for app in NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) where !app.terminate() { printer.warning("Failed to terminate app with bundle ID:", bundleID) if !app.forceTerminate() { printer.error("Failed to force terminate app with bundle ID:", bundleID) } } } let executablePathSet = Set([ "/System/Library/Frameworks/StoreKit.framework/Support/storekitagent", "/System/Library/PrivateFrameworks/AppStoreComponents.framework/Support/appstorecomponentsd", "/System/Library/PrivateFrameworks/AppStoreDaemon.framework/Support/appstoreagent", """ /System/Library/PrivateFrameworks/CascadeSets.framework/Versions/A/XPCServices/SetStoreUpdateService.xpc/Contents/MacOS/SetStoreUpdateService """, "/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storeaccountd", "/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storeassetd", "/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storedownloadd", "/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storeinstalld", "/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storelegacy", ]) var processListMIB = [CTL_KERN, KERN_PROC, KERN_PROC_ALL] var length = 0 guard unsafe sysctl(&processListMIB, u_int(processListMIB.count), nil, &length, nil, 0) == 0 else { printer.error("Failed to get process list length") return } var kinfoProcs = unsafe [kinfo_proc](repeating: kinfo_proc(), count: length / MemoryLayout.stride) guard unsafe sysctl(&processListMIB, u_int(processListMIB.count), &kinfoProcs, &length, nil, 0) == 0 else { printer.error("Failed to get process list") return } var executablePathBuffer = [CChar](repeating: 0, count: .init(PATH_MAX)) for pid in unsafe kinfoProcs.map(\.kp_proc.p_pid) { guard unsafe proc_pidpath(pid, &executablePathBuffer, UInt32(executablePathBuffer.count)) > 0, let executablePath = String(cString: executablePathBuffer, encoding: .utf8), executablePathSet.contains(executablePath) else { continue } let exitStatus = kill(pid, SIGTERM) if exitStatus != 0 { printer.error("Failed to terminate", executablePath, "getting exit status", exitStatus, "for pid", pid) } } let folder = CKDownloadDirectory(nil) do { try FileManager.default.removeItem(atPath: folder) } catch { printer.error("Failed to delete download folder", folder, error: error) } } } } ================================================ FILE: Sources/mas/Commands/Search.swift ================================================ // // Search.swift // mas // // Copyright © 2016 mas-cli. All rights reserved. // internal import ArgumentParser private import Foundation extension MAS { /// Searches for apps in the App Store. /// /// Uses the iTunes Search API: /// /// https://performance-partners.apple.com/search-api struct Search: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Search for apps in the App Store", ) @Flag(help: "Output the price of each app") private var price = false @OptionGroup private var searchTermOptionGroup: SearchTermOptionGroup func run() async throws { try await run(searchForAppsMatchingSearchTerm: search(for:)) } private func run(searchForAppsMatchingSearchTerm: (String) async throws -> [CatalogApp]) async throws { try run(catalogApps: try await searchForAppsMatchingSearchTerm(searchTermOptionGroup.searchTerm)) } func run(catalogApps: [CatalogApp]) throws { // swiftformat:disable:this organizeDeclarations guard let maxADAMIDLength = catalogApps.map({ String(describing: $0.adamID).count }).max(), let maxNameLength = catalogApps.map(\.name.count).max() else { throw MASError.noCatalogAppsFound(for: searchTermOptionGroup.searchTerm) } let format = "%\(maxADAMIDLength)lu %@ (%@)\(price ? " %@" : "")" printer.info( catalogApps.map { catalogApp in String( format: format, catalogApp.adamID, catalogApp.name.padding(toLength: maxNameLength, withPad: " ", startingAt: 0), catalogApp.version, catalogApp.displayPrice, ) } .joined(separator: "\n"), ) } } } ================================================ FILE: Sources/mas/Commands/Seller.swift ================================================ // // Seller.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // internal import ArgumentParser private import Foundation extension MAS { /// Opens apps' seller pages in the default web browser. /// /// Uses the iTunes Lookup API: /// /// https://performance-partners.apple.com/search-api struct Seller: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Open apps' seller pages in the default web browser", aliases: ["vendor"], ) @OptionGroup private var catalogAppIDsOptionGroup: CatalogAppIDsOptionGroup func run() async { await run(lookupAppFromAppID: lookup(appID:)) } private func run(lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp) async { await run(catalogApps: await catalogAppIDsOptionGroup.appIDs.lookupCatalogApps(using: lookupAppFromAppID)) } func run(catalogApps: [CatalogApp]) async { // swiftformat:disable:this organizeDeclarations await run( sellerURLStrings: catalogApps.compactMap { catalogApp in guard let sellerURLString = catalogApp.sellerURLString else { printer.error("No seller website available for ADAM ID", catalogApp.adamID) return nil } return sellerURLString }, ) } private func run(sellerURLStrings: [String]) async { // swiftformat:disable:this organizeDeclarations await sellerURLStrings.forEach(attemptTo: "open") { sellerURLString in guard let url = URL(string: sellerURLString) else { throw MASError.unparsableURL(sellerURLString) } try await url.open() } } } } ================================================ FILE: Sources/mas/Commands/SignOut.swift ================================================ // // SignOut.swift // mas // // Copyright © 2016 mas-cli. All rights reserved. // internal import ArgumentParser private import StoreFoundation extension MAS { /// Signs out of the Apple Account currently signed in to the App Store. struct SignOut: ParsableCommand { static let configuration = CommandConfiguration( commandName: "signout", abstract: "Sign out of the App Store", ) func run() { ISServiceProxy.genericShared().accountService.signOut() } } } ================================================ FILE: Sources/mas/Commands/Uninstall.swift ================================================ // // Uninstall.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // internal import ArgumentParser private import Foundation private import OrderedCollections extension MAS { /// Uninstalls apps installed from the App Store. struct Uninstall: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Uninstall apps installed from the App Store", discussion: requiresRootPrivilegesMessage(), ) /// Flag indicating that uninstall shouldn't be performed. @Flag(name: .customLong("dry-run"), help: "Perform dry run") private var isPerformingDryRun = false @Flag(name: .customLong("all"), help: "Uninstall all App Store apps") private var isUninstallingAll = false @OptionGroup private var installedAppIDsOptionGroup: InstalledAppIDsOptionGroup func validate() throws { if isUninstallingAll != installedAppIDsOptionGroup.appIDs.isEmpty { throw ValidationError( isUninstallingAll ? "Cannot specify both --all & app IDs" // swiftformat:disable:this indent : "Must specify either --all or at least one app ID", ) } } func run() async throws { try run(installedApps: try await installedApps) } private func run(installedApps: [InstalledApp]) throws { let uninstallingAppPathSet = ( isUninstallingAll ? installedApps.map { .bundleID($0.bundleID) } : installedAppIDsOptionGroup.appIDs, ) .reduce(into: OrderedSet()) { uninstallingAppPathSet, appID in let uninstallingApps = installedApps.filter { $0.matches(appID) } guard !uninstallingApps.isEmpty else { printer.error(appID.notInstalledMessage) return } uninstallingAppPathSet.append(contentsOf: uninstallingApps.map(\.path)) } guard !uninstallingAppPathSet.isEmpty else { return } guard !isPerformingDryRun else { printer.notice("Dry run. A wet run would uninstall:\n") for uninstallingAppPath in uninstallingAppPathSet { printer.info(uninstallingAppPath) } return } guard getuid() == 0 else { try sudo(MAS._commandName, args: CommandLine.arguments.dropFirst()) return } let processInfo = ProcessInfo.processInfo let uid = try processInfo.sudoUID let gid = try processInfo.sudoGID let fileManager = FileManager.default for appPath in uninstallingAppPathSet { let attributes = try fileManager.attributesOfItem(atPath: appPath) guard let appUID = attributes[.ownerAccountID] as? uid_t else { printer.error("Failed to get uid of", appPath) continue } guard let appGID = attributes[.groupOwnerAccountID] as? gid_t else { printer.error("Failed to get gid of", appPath) continue } do { try mas.run(asEffectiveUID: 0, andEffectiveGID: 0) { try fileManager.setAttributes([.ownerAccountID: uid, .groupOwnerAccountID: gid], ofItemAtPath: appPath) } } catch { printer.error("Failed to change ownership of", appPath.quoted, "to uid", uid, "& gid", gid, error: error) continue } var chownPath = appPath defer { do { try mas.run(asEffectiveUID: 0, andEffectiveGID: 0) { try fileManager.setAttributes( [.ownerAccountID: appUID, .groupOwnerAccountID: appGID], ofItemAtPath: chownPath, ) } } catch { printer.warning( "Failed to revert ownership of", chownPath.quoted, "back to uid", appUID, "& gid", appGID, error: error, ) } } var uninstalledAppNSURL = NSURL?.none // swiftlint:disable:this legacy_objc_type try unsafe fileManager.trashItem( at: .init(filePath: appPath, directoryHint: .isDirectory), resultingItemURL: &uninstalledAppNSURL, ) guard let uninstalledAppPath = uninstalledAppNSURL?.path else { printer.error( """ Failed to revert ownership of uninstalled \(appPath.quoted) back to uid \(appUID) & gid \(appGID):\ failed to obtain uninstalled app URL """, ) continue } chownPath = uninstalledAppPath printer.info("Uninstalled", appPath.quoted, "to", chownPath.quoted) } } } } ================================================ FILE: Sources/mas/Commands/Update.swift ================================================ // // Update.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // internal import ArgumentParser private import StoreFoundation extension MAS { /// Updates outdated apps installed from the App Store. struct Update: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "Update outdated apps installed from the App Store", discussion: requiresRootPrivilegesMessage(), aliases: ["upgrade"], ) @OptionGroup private var outdatedAppOptionGroup: OutdatedAppOptionGroup @OptionGroup private var forceOptionGroup: ForceOptionGroup @OptionGroup private var verboseOptionGroup: VerboseOptionGroup @OptionGroup private var installedAppIDsOptionGroup: InstalledAppIDsOptionGroup func run() async throws { try await run(installedApps: try await installedApps.filter(!\.isTestFlight), lookupAppFromAppID: lookup(appID:)) } private func run( installedApps: [InstalledApp], lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp, ) async throws { try await run( outdatedApps: forceOptionGroup.force // swiftformat:disable:next indent ? installedApps.filter(for: installedAppIDsOptionGroup.appIDs).map { ($0, "") } : await installedApps.outdatedApps( filterFor: installedAppIDsOptionGroup.appIDs, lookupAppFromAppID: lookupAppFromAppID, accuracy: outdatedAppOptionGroup.accuracy, shouldCheckMinimumOSVersion: outdatedAppOptionGroup.shouldCheckMinimumOSVersion, shouldWarnIfUnknownApp: verboseOptionGroup.verbose, ), ) } private func run(outdatedApps: [OutdatedApp]) async throws { try await AppStore.update.apps(withADAMIDs: outdatedApps.map(\.installedApp.adamID)) } } } ================================================ FILE: Sources/mas/Commands/Version.swift ================================================ // // Version.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // internal import ArgumentParser extension MAS { /// Outputs the version of mas. struct Version: ParsableCommand { static let configuration = CommandConfiguration( abstract: "Output version number", ) func run() { printer.info(version) } } } ================================================ FILE: Sources/mas/Controllers/CatalogApp+ITunesSearch.swift ================================================ // // CatalogApp+ITunesSearch.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // internal import Foundation private import Sextant private import SwiftSoup func lookup(appID: AppID) async throws -> CatalogApp { try await lookup(appID: appID, inRegion: appStoreRegion) } /// Look up app details from the App Store catalog via the iTunes Search API. /// /// https://performance-partners.apple.com/search-api /// /// - Parameters: /// - appID: App ID. /// - region: The ISO 3166-1 alpha-2 region of the storefront in which to /// lookup apps. /// - Returns: A `CatalogApp` for the given `appID` if `appID` is valid. /// - Throws: A `MASError.unknownAppID(appID)` if `appID` is invalid. /// Some other `Error` if any other problem occurs. func lookup( appID: AppID, inRegion region: Region = appStoreRegion, dataFrom dataSource: (URL) async throws -> (Data, URLResponse) = urlSession.data(from:), ) async throws -> CatalogApp { let queryItem = switch appID { case let .adamID(adamID): URLQueryItem(name: "id", value: .init(adamID)) case let .bundleID(bundleID): URLQueryItem(name: "bundleId", value: bundleID) } guard // swiftformat:disable:this wrap wrapArguments let catalogApp = // swiftformat:disable:next indent try await getCatalogApps(from: try url("lookup", queryItem, inRegion: region), dataFrom: dataSource).first else { guard let catalogApp = try await getCatalogApps( from: try url("lookup", queryItem, inRegion: region, additionalQueryItems: []), dataFrom: dataSource, ) .first, catalogApp.supportedDevices?.contains("MacDesktop-MacDesktop") ?? false else { throw MASError.unknownAppID(appID) } return catalogApp.with(minimumOSVersion: await catalogApp.minimumOSVersion(dataFrom: dataSource)) } return catalogApp } private extension CatalogApp { func minimumOSVersion(dataFrom: (URL) async throws -> (Data, URLResponse) = urlSession.data(from:)) async -> String { do { return try await URL(string: appStorePageURLString) .flatMap { url in // swiftformat:disable indent try unsafe SwiftSoup.parse(try await dataFrom(url).0, appStorePageURLString) .select("#serialized-server-data") .first()? .data() .query( string: "$.data[0].data.shelfMapping.information.items[?(@.title == 'Compatibility')].items[?(@.heading == 'Mac')].text", )? .firstMatch(of: minimumOSVersionRegex)? .version } .map(String.init(_:)) ?? minimumOSVersion // swiftformat:enable indent } catch { return minimumOSVersion } } } func search(for searchTerm: String) async throws -> [CatalogApp] { try await search(for: searchTerm, inRegion: appStoreRegion) } /// Search for app details from the App Store catalog via the iTunes Search API. /// /// https://performance-partners.apple.com/search-api /// /// - Parameters: /// - searchTerm: Term for which to search. /// - region: The ISO 3166-1 alpha-2 region of the storefront in which to /// search for apps. /// - Returns: A `[CatalogApp]` matching `searchTerm`. /// - Throws: An `Error` if any problem occurs. func search( for searchTerm: String, inRegion region: Region = appStoreRegion, dataFrom dataSource: @escaping @Sendable (URL) async throws -> (Data, URLResponse) = urlSession.data(from:), ) async throws -> [CatalogApp] { let queryItem = URLQueryItem(name: "term", value: searchTerm) let catalogApps = try await getCatalogApps(from: try url("search", queryItem, inRegion: region), dataFrom: dataSource) let adamIDSet = Set(catalogApps.map(\.adamID)) return catalogApps.priorityMerge( try await getCatalogApps( from: try url("search", queryItem, inRegion: region, additionalQueryItems: []), dataFrom: dataSource, ) .filter { ($0.supportedDevices?.contains("MacDesktop-MacDesktop") ?? false) && !adamIDSet.contains($0.adamID) } .concurrentMap { $0.with(minimumOSVersion: await $0.minimumOSVersion(dataFrom: dataSource)) }, ) { $0.name.similarity(to: searchTerm) } } private func url( _ action: String, _ queryItem: URLQueryItem, inRegion region: Region, additionalQueryItems: [URLQueryItem] = [URLQueryItem(name: "entity", value: "desktopSoftware")], ) throws -> URL { let urlString = "https://itunes.apple.com/\(action)" guard let url = URL(string: urlString) else { throw MASError.unparsableURL(urlString) } return url.appending( queryItems: [URLQueryItem(name: "media", value: "software")] + additionalQueryItems // swiftformat:disable indent + [ URLQueryItem(name: "country", value: region), queryItem, ], ) // swiftformat:enable indent } private func getCatalogApps(from url: URL, dataFrom: (URL) async throws -> (Data, URLResponse)) async throws -> [CatalogApp] { // swiftformat:disable:this indent let (data, _) = try await dataFrom(url) do { return try JSONDecoder().decode(CatalogAppResults.self, from: data).results } catch { throw MASError.error("Failed to parse JSON from response \(url)", error: .init(data: data, encoding: .utf8) ?? "") } } private let urlSession = URLSession(configuration: .ephemeral) private nonisolated(unsafe) let minimumOSVersionRegex = /macOS\s*(?\S+)/ ================================================ FILE: Sources/mas/Controllers/InstalledApp+Spotlight.swift ================================================ // // InstalledApp+Spotlight.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import Atomics private import Foundation private import ObjectiveC private extension URL { var installedAppURLs: [URL] { FileManager.default // swiftformat:disable indent .enumerator(at: self, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles]) .map { enumerator in enumerator.compactMap { item in guard let url = item as? URL, (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) == true, url.pathExtension == "app" else { return nil as URL? } enumerator.skipDescendants() return try? url.appending(path: "Contents/_MASReceipt/receipt", directoryHint: .notDirectory) .resourceValues(forKeys: [.fileSizeKey]) .fileSize .flatMap { $0 > 0 ? url : nil } } } ?? [] } // swiftformat:enable indent } var installedApps: [InstalledApp] { get async throws { try await mas.installedApps(matching: "kMDItemAppStoreAdamID LIKE '*'") } } func installedApps(withADAMID adamID: ADAMID) async throws -> [InstalledApp] { try await installedApps(matching: "kMDItemAppStoreAdamID = \(adamID)") } @MainActor func installedApps(matching metadataQuery: String) async throws -> [InstalledApp] { var observer = (any NSObjectProtocol)?.none defer { if let observer { NotificationCenter.default.removeObserver(observer) } } let query = NSMetadataQuery() query.predicate = NSPredicate(format: metadataQuery) query.searchScopes = applicationsFolderURLs return try await withCheckedThrowingContinuation { continuation in let alreadyResumed = ManagedAtomic(false) observer = NotificationCenter.default.addObserver( forName: .NSMetadataQueryDidFinishGathering, object: query, queue: nil, ) { notification in guard !alreadyResumed.exchange(true, ordering: .acquiringAndReleasing) else { return } guard let query = notification.object as? NSMetadataQuery else { continuation.resume( throwing: MASError.error( "Notification Center returned a \(type(of: notification.object)) instead of a NSMetadataQuery", ), ) return } query.stop() let installedApps = query.results .compactMap { result in // swiftformat:disable indent (result as? NSMetadataItem).map { item in InstalledApp( adamID: item.value(forAttribute: "kMDItemAppStoreAdamID") as? ADAMID ?? 0, bundleID: item.value(forAttribute: NSMetadataItemCFBundleIdentifierKey) as? String ?? "", name: (item.value(forAttribute: "_kMDItemDisplayNameWithExtensions") as? String ?? "") .removingSuffix(".app"), path: item.value(forAttribute: NSMetadataItemPathKey) as? String ?? "", version: item.value(forAttribute: NSMetadataItemVersionKey) as? String ?? "", ) } } .sorted(using: KeyPathComparator(\.name, comparator: .localizedStandard)) // swiftformat:enable indent if !["1", "true", "yes"].contains(ProcessInfo.processInfo.environment["MAS_NO_AUTO_INDEX"]?.lowercased()) { let installedAppPathSet = Set(installedApps.map(\.path)) for installedAppURL in applicationsFolderURLs.flatMap(\.installedAppURLs) where !installedAppPathSet.contains(installedAppURL.filePath) { // swiftformat:disable:this indent MAS.printer.warning( "Found a likely App Store app that is not indexed in Spotlight in ", installedAppURL.filePath, """ Indexing now, which will not complete until sometime after mas exits Disable auto-indexing via: export MAS_NO_AUTO_INDEX=1 """, separator: "", ) Task { do { _ = try await run( "/usr/bin/mdimport", installedAppURL.filePath, errorMessage: "Failed to index the Spotlight data for \(installedAppURL.filePath)", ) } catch { MAS.printer.error(error: error) } } } } continuation.resume(returning: installedApps) } query.start() } } ================================================ FILE: Sources/mas/Errors/MASError.swift ================================================ // // MASError.swift // mas // // Copyright © 2015 mas-cli. All rights reserved. // enum MASError: Error { case error(String, error: (any Error)? = nil, separator: String = ":\n", separatorAndErrorReplacement: String = "") case noCatalogAppsFound(for: String) case unknownAppID(AppID) case unparsableURL(String) static func error( _ message: String, error: String?, separator: String = ":\n", separatorAndErrorReplacement: String = "", ) -> Self { .error( message, error: error.map { Self.error($0) }, separator: separator, separatorAndErrorReplacement: separatorAndErrorReplacement, ) } } extension MASError: CustomStringConvertible { var description: String { switch self { case let .error(message, error, separator, separatorAndErrorReplacement): "\(message)\(error.map { "\(separator)\($0)" } ?? separatorAndErrorReplacement)" case let .noCatalogAppsFound(searchTerm): "No apps found in the App Store for search term: \(searchTerm)" case let .unknownAppID(appID): "No apps found in the App Store for \(appID)" case let .unparsableURL(string): "Failed to parse URL from \(string)" } } } ================================================ FILE: Sources/mas/Models/AppID.swift ================================================ // // AppID.swift // mas // // Copyright © 2024 mas-cli. All rights reserved. // enum AppID: CustomStringConvertible { case adamID(ADAMID) case bundleID(String) var description: String { switch self { case let .adamID(adamID): "ADAM ID \(adamID)" case let .bundleID(bundleID): "bundle ID \(bundleID)" } } var notInstalledMessage: String { "No installed apps with \(self)" } init(from string: String, forceBundleID: Bool = false) { guard !forceBundleID, let adamID = ADAMID(string) else { self = .bundleID(string) return } self = .adamID(adamID) } } extension [AppID] { // swiftlint:disable:this file_types_order func lookupCatalogApps(using lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp) async -> [CatalogApp] { // swiftformat:disable:this indent await concurrentCompactMap(attemptingTo: "lookup app for", lookupAppFromAppID) } } typealias ADAMID = UInt64 ================================================ FILE: Sources/mas/Models/CatalogApp.swift ================================================ // // CatalogApp.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // struct CatalogApp { let adamID: ADAMID let appStorePageURLString: String let bundleID: String let fileSizeBytes: String let formattedPrice: String? let minimumOSVersion: String let name: String let releaseDate: String let sellerName: String let sellerURLString: String? let supportedDevices: [String]? // swiftlint:disable:this discouraged_optional_collection let version: String var displayPrice: String { formattedPrice ?? "?" } init( adamID: ADAMID = 0, appStorePageURLString: String = "", bundleID: String = "", fileSizeBytes: String = "?", formattedPrice: String? = "?", minimumOSVersion: String = "", name: String = "", releaseDate: String = "", sellerName: String = "", sellerURLString: String? = nil, supportedDevices: [String]? = nil, // swiftlint:disable:this discouraged_optional_collection version: String = "", ) { self.adamID = adamID self.appStorePageURLString = appStorePageURLString self.bundleID = bundleID self.fileSizeBytes = fileSizeBytes self.formattedPrice = formattedPrice self.minimumOSVersion = minimumOSVersion self.name = name self.releaseDate = releaseDate self.sellerName = sellerName self.sellerURLString = sellerURLString self.supportedDevices = supportedDevices self.version = version } func with(minimumOSVersion: String) -> Self { .init( adamID: adamID, appStorePageURLString: appStorePageURLString, bundleID: bundleID, fileSizeBytes: fileSizeBytes, formattedPrice: formattedPrice, minimumOSVersion: minimumOSVersion, name: name, releaseDate: releaseDate, sellerName: sellerName, sellerURLString: sellerURLString, supportedDevices: supportedDevices, version: version, ) } } extension CatalogApp: Decodable { enum CodingKeys: String, CodingKey { case adamID = "trackId" case appStorePageURLString = "trackViewUrl" case bundleID = "bundleId" case fileSizeBytes case formattedPrice case minimumOSVersion = "minimumOsVersion" case name = "trackName" case releaseDate = "currentVersionReleaseDate" case sellerName case sellerURLString = "sellerUrl" case supportedDevices case version } } ================================================ FILE: Sources/mas/Models/CatalogAppResults.swift ================================================ // // CatalogAppResults.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // struct CatalogAppResults: Decodable { // swiftlint:disable:next unused_declaration let resultCount: Int // periphery:ignore let results: [CatalogApp] } ================================================ FILE: Sources/mas/Models/InstalledApp.swift ================================================ // // InstalledApp.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // struct InstalledApp { let adamID: ADAMID let bundleID: String let name: String let path: String let version: String var isTestFlight: Bool { adamID == 0 } func matches(_ appID: AppID) -> Bool { switch appID { case let .adamID(adamID): self.adamID == adamID case let .bundleID(bundleID): self.bundleID == bundleID } } } extension [InstalledApp] { func filter(for appIDs: [AppID]) -> [Element] { appIDs.isEmpty ? self // swiftformat:disable:this indent : appIDs.flatMap { appID in let installedApps = filter { $0.matches(appID) } if installedApps.isEmpty { MAS.printer.error(appID.notInstalledMessage) } return installedApps } } } ================================================ FILE: Sources/mas/Models/OutdatedApp.swift ================================================ // // OutdatedApp.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import ArgumentParser private import Atomics private import Foundation private import StoreFoundation typealias OutdatedApp = ( installedApp: InstalledApp, newVersion: String, ) extension [InstalledApp] { func outdatedApps( filterFor appIDs: [AppID], // swiftlint:disable:next unneeded_escaping lookupAppFromAppID: @escaping @Sendable (AppID) async throws -> CatalogApp, accuracy: OutdatedAccuracy, shouldCheckMinimumOSVersion: Bool, shouldWarnIfUnknownApp: Bool, ) async -> [OutdatedApp] { @Sendable func installableCatalogApp(from installedApp: InstalledApp) async -> CatalogApp? { do { let catalogApp = try await lookupAppFromAppID(.bundleID(installedApp.bundleID)) return shouldCheckMinimumOSVersion // swiftformat:disable indent && UniversalSemVerInt(from: catalogApp.minimumOSVersion).flatMap { minimumOSVersion in ProcessInfo.processInfo.isOperatingSystemAtLeast( OperatingSystemVersion( majorVersion: minimumOSVersion.majorInteger, minorVersion: minimumOSVersion.minorInteger, patchVersion: minimumOSVersion.patchInteger, ), ) } == false ? nil : catalogApp } catch { // swiftformat:enable indent if let error = error as? MASError, case MASError.unknownAppID = error { if shouldWarnIfUnknownApp { MAS.printer.warning(error, "; was expected to identify: ", installedApp.name, separator: "") } } else { MAS.printer.error(error: error) } return nil } } return await filter(for: appIDs).concurrentCompactMap( accuracy == .accurate ? { @Sendable installedApp in // swiftformat:disable indent if shouldCheckMinimumOSVersion, await installableCatalogApp(from: installedApp) == nil { return nil } return await withCheckedContinuation { continuation in Task { let alreadyResumed = ManagedAtomic(false) do { try await AppStore.install.app(withADAMID: installedApp.adamID) { appStoreVersion, shouldOutput in if shouldOutput, let appStoreVersion, installedApp.version != appStoreVersion, !alreadyResumed.exchange(true, ordering: .acquiringAndReleasing) { continuation.resume(returning: OutdatedApp(installedApp, appStoreVersion)) } return true } } catch { MAS.printer.error(error: error) } if !alreadyResumed.load(ordering: .acquiring) { continuation.resume(returning: nil) } } } } : { @Sendable installedApp in await installableCatalogApp(from: installedApp).flatMap { catalogApp in UniversalSemVer(from: installedApp.version).compareSemVerAndBuild(to: .init(from: catalogApp.version)) == .orderedAscending ? OutdatedApp(installedApp, catalogApp.version) : nil } }, ) // swiftformat:enable indent } } ================================================ FILE: Sources/mas/Network/URL.swift ================================================ // // URL.swift // mas // // Copyright © 2024 mas-cli. All rights reserved. // internal import AppKit private import Foundation private import ObjectiveC extension URL { var filePath: String { .init(path(percentEncoded: false).dropLast { $0 == "/" }) } func open(configuration: NSWorkspace.OpenConfiguration = NSWorkspace.OpenConfiguration()) async throws { try await NSWorkspace.shared.open(self, configuration: configuration) } } ================================================ FILE: Sources/mas/Utilities/Collection.swift ================================================ // // Collection.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // extension Collection { func dropLast(while predicate: (Element) throws -> Bool) rethrows -> SubSequence { try indices.reversed().first { try !predicate(self[$0]) }.map { self[...$0] } ?? self[endIndex...] } } extension Collection where Element: Sendable { func concurrentMap( maxConcurrentTaskCount: Int = defaultMaxConcurrentTaskCount, _ transform: @escaping @Sendable (Element) async -> T, ) async -> [T] { // swiftlint:disable:next force_unwrapping await concurrentTransform(maxConcurrentTaskCount: maxConcurrentTaskCount, transform).map { $0! } } func concurrentMap( maxConcurrentTaskCount: Int = defaultMaxConcurrentTaskCount, _ transform: @escaping @Sendable (Element) async throws -> T, ) async rethrows -> [T] { // periphery:ignore try await concurrentTransform(maxConcurrentTaskCount: maxConcurrentTaskCount, transform).map { $0! } } // swiftlint:disable:previous force_unwrapping func concurrentCompactMap( maxConcurrentTaskCount: Int = defaultMaxConcurrentTaskCount, _ transform: @escaping @Sendable (Element) async -> T?, ) async -> [T] { await concurrentTransform(maxConcurrentTaskCount: maxConcurrentTaskCount, transform).compactMap(\.self) } func concurrentCompactMap( maxConcurrentTaskCount: Int = defaultMaxConcurrentTaskCount, _ transform: @escaping @Sendable (Element) async throws -> T?, ) async rethrows -> [T] { // periphery:ignore try await concurrentTransform(maxConcurrentTaskCount: maxConcurrentTaskCount, transform).compactMap(\.self) } func concurrentCompactMap( attemptingTo perform: String, maxConcurrentTaskCount: Int = defaultMaxConcurrentTaskCount, _ transform: @escaping @Sendable (Element) async throws(E) -> T?, ) async -> [T] { await concurrentCompactMap(maxConcurrentTaskCount: maxConcurrentTaskCount) { element in do { return try await transform(element) } catch { MAS.printer.error(error is MASError ? [] : ["Failed to", perform, element], error: error) return nil } } } private func concurrentTransform( maxConcurrentTaskCount: Int, _ transform: @escaping @Sendable (Element) async throws -> T?, ) async rethrows -> [T?] { try await withThrowingTaskGroup(of: (index: Int, result: T?).self) { group in var iterator = enumerated().makeIterator() func addNextTask() { if let next = iterator.next() { group.addTask { (next.offset, try await transform(next.element)) } } } for _ in 0..(keyPath: KeyPath) -> (Root) -> Bool { // swiftlint:disable:this static_operator { !$0[keyPath: keyPath] } } ================================================ FILE: Sources/mas/Utilities/Optional.swift ================================================ // // Optional.swift // mas // // Copyright © 2026 mas-cli. All rights reserved. // extension Optional { // periphery:ignore func map(_ transform: (Wrapped) async throws(E) -> U) async throws(E) -> U? { guard let self else { // swiftlint:disable:previous unused_declaration return nil } return try await transform(self) } func flatMap(_ transform: (Wrapped) async throws(E) -> U?) async throws(E) -> U? { guard let self else { return nil } return try await transform(self) } } ================================================ FILE: Sources/mas/Utilities/Pipe.swift ================================================ // // Pipe.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import Foundation extension Pipe { func readToEnd(encoding: String.Encoding = .utf8) throws -> String? { try fileHandleForReading.readToEnd().flatMap { .init(data: $0, encoding: encoding) } } } ================================================ FILE: Sources/mas/Utilities/Printer.swift ================================================ // // Printer.swift // mas // // Copyright © 2016 mas-cli. All rights reserved. // private import ArgumentParser private import Atomics internal import Foundation /// Prints to `stdout` and `stderr` with ANSI color codes when connected to a /// terminal. struct Printer { private let errorCounter = ManagedAtomic(0) var errorCount: UInt64 { errorCounter.load(ordering: .acquiring) } func resetErrorCount() { // periphery:ignore errorCounter.store(0, ordering: .releasing) // swiftlint:disable:previous unused_declaration } /// Prints to `stdout`. @_disfavoredOverload func info(_ items: Any..., separator: String = " ", terminator: String = "\n") { info(items, separator: separator, terminator: terminator) } /// Prints to `stdout`. func info(_ items: [Any], separator: String = " ", terminator: String = "\n") { print(items.map(String.init(describing:)), separator: separator, terminator: terminator, to: .standardOutput) } /// Prints to `stdout`, prefixed with "==> "; if connected to a terminal, the /// prefix is blue. @_disfavoredOverload func notice(_ items: Any..., separator: String = " ", terminator: String = "\n") { notice(items, separator: separator, terminator: terminator) } /// Prints to `stdout`, prefixed with "==> "; if connected to a terminal, the /// prefix is blue. func notice(_ items: [Any], separator: String = " ", terminator: String = "\n") { print(items, prefix: "==>", format: "1;34", separator: separator, terminator: terminator, to: .standardOutput) } /// Prints to `stderr`, prefixed with "Warning: "; if connected to a terminal, /// the prefix is yellow & underlined. @_disfavoredOverload func warning(_ items: Any..., error: (any Error)? = nil, separator: String = " ", terminator: String = "\n") { warning(items, error: error, separator: separator, terminator: terminator) } /// Prints to `stderr`, prefixed with "Warning: "; if connected to a terminal, /// the prefix is yellow & underlined. func warning(_ items: [Any], error: (any Error)? = nil, separator: String = " ", terminator: String = "\n") { problem(items, prefix: "Warning:", format: "4;33", error: error, separator: separator, terminator: terminator) } /// Prints to `stderr`, prefixed with "Error: "; if connected to a terminal, /// the prefix is red & underlined. @_disfavoredOverload func error(_ items: Any..., error: (any Error)? = nil, separator: String = " ", terminator: String = "\n") { self.error(items, error: error, separator: separator, terminator: terminator) } /// Prints to `stderr`, prefixed with "Error: "; if connected to a terminal, /// the prefix is red & underlined. func error(_ items: [Any], error: (any Error)? = nil, separator: String = " ", terminator: String = "\n") { errorCounter.wrappingIncrement(ordering: .relaxed) problem(items, prefix: errorPrefix, format: errorFormat, error: error, separator: separator, terminator: terminator) } func clearCurrentLine(of fileHandle: FileHandle) { if fileHandle.isTerminal { do { try fileHandle.write(contentsOf: Data("\(csi)2K\(csi)0G".utf8)) } catch { // Do nothing } } } private func problem( _ items: [Any], prefix: String, format: String, error: (any Error)?, separator: String, terminator: String, ) { guard !items.isEmpty || (error != nil && !(error is ExitCode)) else { return } print( items, prefix: prefix, format: format, separator: separator, terminator: error.map { error in let errorDescription = String(describing: error) return "\(errorDescription.isEmpty ? "" : items.isEmpty ? " " : "\n")\(errorDescription)\(terminator)" } ?? terminator, // swiftformat:disable:this indent to: .standardError, ) } private func print(_ items: [String], separator: String, terminator: String, to fileHandle: FileHandle) { do { try fileHandle.write(contentsOf: Data(items.joined(separator: separator).appending(terminator).utf8)) } catch { // Do nothing } } private func print( _ items: [Any], prefix: String, format: String, separator: String, terminator: String, to fileHandle: FileHandle, ) { // swiftformat:disable indent let indent = """ \( String( // swiftlint:disable:this indentation_width repeating: " ", count: (prefix.range(of: "\n", options: .backwards).map { .init(prefix[$0.upperBound...]) } ?? prefix).count + 1, ) ) """ let formattedPrefix = prefix.formatted(with: format, for: fileHandle) // swiftformat:enable indent print( items.first.map { item in ["\(formattedPrefix) \(mas.indent(item, with: indent))"] + items.dropFirst().map { mas.indent($0, with: indent) } // swiftformat:disable:this indent } ?? [formattedPrefix], // swiftformat:disable:this indent separator: mas.indent(separator, with: indent), terminator: terminator, to: fileHandle, ) } } extension String { func formatted(with format: Self, for fileHandle: FileHandle) -> Self { fileHandle.isTerminal ? "\(csi)\(format)m\(self)\(csi)0m" : self } } private func indent(_ item: Any, with indent: String) -> String { .init(describing: item).replacing(unsafe nonEmptyLineStartRegex, with: indent) } let errorPrefix = "Error:" let errorFormat = "4;31" /// Terminal Control Sequence Indicator. private let csi = "\u{001B}[" private nonisolated(unsafe) let nonEmptyLineStartRegex = /\n(?!\n)/ ================================================ FILE: Sources/mas/Utilities/Process.swift ================================================ // // Process.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import Foundation private import ObjectiveC func run( _ executablePath: String, _ args: String..., errorMessage: @autoclosure () -> String, runProcess run: (Process) throws -> Void = { try $0.run() }, ) async throws -> (standardOutputString: String, standardErrorString: String) { let process = Process() process.executableURL = URL(filePath: executablePath, directoryHint: .notDirectory) process.arguments = args let standardOutputPipe = Pipe() let standardErrorPipe = Pipe() process.standardOutput = standardOutputPipe process.standardError = standardErrorPipe let standardOutputTask = Task(priority: .background) { try standardOutputPipe.readToEnd()?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" } let standardErrorTask = Task(priority: .background) { try standardErrorPipe.readToEnd()?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" } do { try run(process) } catch { throw MASError.error(errorMessage(), error: error) } process.waitUntilExit() let standardOutputString = try await standardOutputTask.value let standardErrorString = try await standardErrorTask.value guard process.terminationStatus == 0 else { throw MASError.error( """ \(errorMessage()) Exit status: \(process.terminationStatus)\ \(standardOutputString.ifNotEmptyPrepend("\n\nStandard output:\n"))\ \(standardErrorString.ifNotEmptyPrepend("\n\nStandard error:\n")) """, ) } return (standardOutputString, standardErrorString) } private extension String { func ifNotEmptyPrepend(_ prefix: String) -> Self { isEmpty ? self : prefix + self } } ================================================ FILE: Sources/mas/Utilities/ProcessInfo.swift ================================================ // // ProcessInfo.swift // mas // // Copyright © 2024 mas-cli. All rights reserved. // internal import Darwin private import Foundation extension ProcessInfo { var sudoUID: uid_t { get throws { try environment["SUDO_UID"].flatMap(uid_t.init) ?? { throw MASError.error("Failed to get sudo uid") }() } } var sudoGID: gid_t { get throws { try environment["SUDO_GID"].flatMap(gid_t.init) ?? { throw MASError.error("Failed to get sudo gid") }() } } func runAsSudoEffectiveUserAndSudoEffectiveGroup(_ body: () throws -> T) throws -> T { try run(asEffectiveUID: sudoUID, andEffectiveGID: sudoGID, body) } func runAsSudoEffectiveUserAndSudoEffectiveGroup(_ body: () async throws -> T) async throws -> T { try await run(asEffectiveUID: sudoUID, andEffectiveGID: sudoGID, body) } func runAsSudoEffectiveUserAndSudoEffectiveGroupIfRootEffectiveUser(_ body: () throws -> T) throws -> T { geteuid() == 0 ? try runAsSudoEffectiveUserAndSudoEffectiveGroup(body) : try body() } func runAsSudoEffectiveUserAndSudoEffectiveGroupIfRootEffectiveUser(_ body: () async throws -> T) async throws -> T { // swiftformat:disable:this indent geteuid() == 0 ? try await runAsSudoEffectiveUserAndSudoEffectiveGroup(body) : try await body() } } ================================================ FILE: Sources/mas/Utilities/RangeReplaceableCollection.swift ================================================ // // RangeReplaceableCollection.swift // mas // // Copyright © 2026 mas-cli. All rights reserved. // extension RangeReplaceableCollection { func padding(toCount minimumCount: Int, with padValue: Element) -> Self { let appendCount = minimumCount - count return appendCount > 0 ? self + repeatElement(padValue, count: appendCount) : self } } ================================================ FILE: Sources/mas/Utilities/Sequence.swift ================================================ // // Sequence.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // extension Sequence { func forEach(attemptTo perform: String, _ body: (Element) async throws(E) -> Void) async { await forEach(body) { MAS.printer.error($1 is MASError ? [] : ["Failed to", perform, $0], error: $1) } } private func forEach( _ body: (Element) async throws(E) -> Void, handlingErrors errorHandler: (Element, E) async -> Void, ) async { for element in self { do { try await body(element) } catch { await errorHandler(element, error) } } } } extension Sequence { /// Merge two sequences by greedily selecting the element with the higher score. /// Preserves the relative order of elements within their original sequences. func priorityMerge(_ secondary: some Sequence, score: (Element) -> Double) -> [Element] { var merged = [Element]() if let primary = self as? any Collection, let secondary = secondary as? any Collection { merged.reserveCapacity(primary.count + secondary.count) } var primaryIterator = makeIterator() var secondaryIterator = secondary.makeIterator() var primaryItemAndScore: (item: Element, score: Double)? = primaryIterator.next().map { ($0, score($0)) } var secondaryItemAndScore: (item: Element, score: Double)? = secondaryIterator.next().map { ($0, score($0)) } while let primaryInfo = primaryItemAndScore, let secondaryInfo = secondaryItemAndScore { if primaryInfo.score >= secondaryInfo.score { merged.append(primaryInfo.item) primaryItemAndScore = primaryIterator.next().map { ($0, score($0)) } } else { merged.append(secondaryInfo.item) secondaryItemAndScore = secondaryIterator.next().map { ($0, score($0)) } } } if let primaryItemAndScore { merged.append(primaryItemAndScore.item) while let item = primaryIterator.next() { merged.append(item) } } else if let secondaryItemAndScore { merged.append(secondaryItemAndScore.item) while let item = secondaryIterator.next() { merged.append(item) } } return merged } } ================================================ FILE: Sources/mas/Utilities/String.swift ================================================ // // String.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import Foundation extension String { var quoted: Self { "'\(replacing("'", with: "\\'"))'" } func removingSuffix(_ suffix: Self) -> Self { hasSuffix(suffix) ? Self(dropLast(suffix.count)) : self } func similarity(to other: Self) -> Double { let this = Array(precomposedStringWithCanonicalMapping) let that = Array(other.precomposedStringWithCanonicalMapping) let thisLength = this.count let thatLength = that.count guard thisLength > 0 || thatLength > 0 else { return 1.0 } guard thisLength != 0, thatLength != 0 else { return 0.0 } // 2D matrix for Damerau-Levenshtein to track transpositions var matrix = Array(repeating: Array(repeating: 0.0, count: thatLength + 1), count: thisLength + 1) // Initialize base costs (deletions/insertions) for index in 0...thisLength { matrix[index][0] = Double(index) } for index in 0...thatLength { matrix[0][index] = Double(index) } for i in 1...thisLength { // swiftlint:disable:this identifier_name for j in 1...thatLength { // swiftlint:disable:this identifier_name let thisChar = this[i - 1] let thatChar = that[j - 1] if thisChar == thatChar { matrix[i][j] = matrix[i - 1][j - 1] } else { // Standard edit costs let cost = Swift.min( matrix[i - 1][j] + thisChar.structuralCost, // Deletion matrix[i][j - 1] + thatChar.structuralCost, // Insertion matrix[i - 1][j - 1] + thisChar.substitutionCost(for: thatChar), ) matrix[i][j] = i > 1 && j > 1 && this[i - 1] == that[j - 2] && this[i - 2] == that[j - 1] // Transposition ? Swift.min(cost, matrix[i - 2][j - 2] + 0.4) // swiftformat:disable:this indent : cost } } } return max(0, 1.0 - (matrix[thisLength][thatLength] / Double(max(thisLength, thatLength)))) } } private extension Character { var structuralCost: Double { isWhitespace || isPunctuation || isSymbol ? 0.25 : 1.0 } func substitutionCost(for char: Character) -> Double { String(self).folding(options: .diacriticInsensitive, locale: .current) == String(char).folding(options: .diacriticInsensitive, locale: .current) // swiftformat:disable:this indent ? 0.1 // swiftformat:disable:this indent : lowercased() == char.lowercased() ? 0.2 : 1.0 } } ================================================ FILE: Sources/mas/Utilities/Sudo.swift ================================================ // // Sudo.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import ArgumentParser private import Darwin private import Foundation func sudo(_ executableName: String, args: some Sequence) throws { guard let executablePath = Bundle.main.executableURL?.filePath else { throw MASError.error("Failed to get the executable path for sudo \(executableName) \(args.joined(separator: " "))") } try sudo([executablePath] + args) } private func sudo(_ args: some Sequence) throws { let cArgs = unsafe (["sudo", "MAS_NO_AUTO_INDEX=1"] + args).map { unsafe strdup($0) } defer { for unsafe cArg in unsafe cArgs { unsafe free(cArg) } } var pid = 0 as pid_t let spawnStatus = unsafe posix_spawn(&pid, "/usr/bin/sudo", nil, nil, cArgs + [nil], environ) guard spawnStatus == 0 else { throw MASError.error( "Failed to spawn installer process", error: unsafe String(cString: strerror(spawnStatus)), separator: ": ", ) } var sudoStatus = 0 as Int32 unsafe waitpid(pid, &sudoStatus, 0) guard sudoStatus == 0 else { throw ExitCode(max((sudoStatus >> 8) & 0xff, 1)) } } ================================================ FILE: Sources/mas/Utilities/User.swift ================================================ // // User.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import Darwin private extension uid_t { var nameAndID: String { "\(unsafe String(cString: unsafe getpwuid(self).pointee.pw_name).quoted) (\(self))" } } func set(effectiveUID uid: uid_t) throws { guard seteuid(uid) == 0 else { throw MASError.error("Failed to switch effective user from \(geteuid().nameAndID) to \(uid.nameAndID)") } } func reset(effectiveUID uid: uid_t) { do { try set(effectiveUID: uid) } catch { MAS.printer.warning(error: error) } } ================================================ FILE: Sources/mas/Utilities/UserAndGroup.swift ================================================ // // UserAndGroup.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // internal import Darwin func run(asEffectiveUID uid: uid_t, andEffectiveGID gid: gid_t, _ body: () throws -> T) throws -> T { let originalEffectiveUID = geteuid() let originalEffectiveGID = getegid() guard originalEffectiveUID == 0 else { try set(effectiveUID: uid) defer { reset(effectiveUID: originalEffectiveUID) } try set(effectiveGID: gid) defer { reset(effectiveGID: originalEffectiveGID) } return try body() } try set(effectiveGID: gid) defer { reset(effectiveGID: originalEffectiveGID) } try set(effectiveUID: uid) defer { reset(effectiveUID: originalEffectiveUID) } return try body() } func run(asEffectiveUID uid: uid_t, andEffectiveGID gid: gid_t, _ body: () async throws -> T) async throws -> T { let originalEffectiveUID = geteuid() let originalEffectiveGID = getegid() guard originalEffectiveUID == 0 else { try set(effectiveUID: uid) defer { reset(effectiveUID: originalEffectiveUID) } try set(effectiveGID: gid) defer { reset(effectiveGID: originalEffectiveGID) } return try await body() } try set(effectiveGID: gid) defer { reset(effectiveGID: originalEffectiveGID) } try set(effectiveUID: uid) defer { reset(effectiveUID: originalEffectiveUID) } return try await body() } ================================================ FILE: Sources/mas/Utilities/Version+SemVer.swift ================================================ // // Version+SemVer.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import BigInt internal import Foundation // swiftlint:disable:next blanket_disable_command // swiftlint:disable file_types_order one_declaration_per_file protocol Version: CustomStringConvertible { var coreElements: [String] { get } var prereleaseElements: [String] { get } var buildElements: [String] { get } var core: String { get } var prerelease: String? { get } var build: String? { get } init?(from versionString: String) // periphery:ignore } protocol CoreIntegerVersion: Version { associatedtype Integer: BinaryInteger var coreIntegers: [Integer] { get } } extension CoreIntegerVersion where Integer: FixedWidthInteger { var coreElements: [String] { coreIntegers.map { .init($0) } } } protocol MajorMinorPatch { // swiftlint:disable unused_declaration var major: String { get } // periphery:ignore var minor: String { get } // periphery:ignore var patch: String { get } // periphery:ignore } // swiftlint:enable unused_declaration protocol MajorMinorPatchInteger: MajorMinorPatch { associatedtype Integer: BinaryInteger var majorInteger: Integer { get } var minorInteger: Integer { get } var patchInteger: Integer { get } } extension MajorMinorPatchInteger { var major: String { .init(majorInteger) } var minor: String { .init(minorInteger) } var patch: String { .init(patchInteger) } } protocol SemVerSyntax: Version {} extension SemVerSyntax { var core: String { coreElements.joined(separator: ".") } var prerelease: String? { prereleaseElements.isEmpty ? nil : prereleaseElements.joined(separator: ".") } var build: String? { buildElements.isEmpty ? nil : buildElements.joined(separator: ".") } var description: String { "\(core)\(prerelease.map { "-\($0)" } ?? "")\(build.map { "+\($0)" } ?? "")" } } protocol SemVerSyntaxInteger: CoreIntegerVersion, SemVerSyntax, MajorMinorPatchInteger {} struct UniversalSemVerInt: SemVerSyntaxInteger { typealias Integer = Int let coreIntegers: [Integer] let prereleaseElements: [String] let buildElements: [String] var majorInteger: Integer { coreIntegers[0] } var minorInteger: Integer { coreIntegers[1] } var patchInteger: Integer { coreIntegers[2] } init( coreIntegers: [Integer], prereleaseElements: [String] = [], buildElements: [String] = [], ) { self.coreIntegers = coreIntegers.padding(toCount: 3, with: 0) self.prereleaseElements = prereleaseElements self.buildElements = buildElements } init?(from versionString: String) { do { let match = versionString.wholeMatch(of: unsafe universalSemVerRegex)! // swiftlint:disable:this force_unwrapping self = .init( coreIntegers: try match.1.elements.map { coreElement in guard let coreInteger = Integer(coreElement) else { throw MASError.error(coreElement) } return coreInteger }, prereleaseElements: match.2.elements, buildElements: match.3.elements, ) } catch { return nil } } } struct UniversalSemVer: SemVerSyntax { let coreElements: [String] let prereleaseElements: [String] let buildElements: [String] init(from versionString: String) { let match = versionString.wholeMatch(of: unsafe universalSemVerRegex)! // swiftlint:disable:this force_unwrapping coreElements = match.1.elements prereleaseElements = match.2.elements buildElements = match.3.elements } } private extension BigUInt { func compare(to that: Self) -> ComparisonResult { self < that ? .orderedAscending : self == that ? .orderedSame : .orderedDescending } } private extension FixedWidthInteger { func compare(to that: Self) -> ComparisonResult { self < that ? .orderedAscending : self == that ? .orderedSame : .orderedDescending } } private extension String { func compareSemVerElement( to that: Self, options mask: CompareOptions = [], range: Range? = nil, locale: Locale? = nil, ) -> ComparisonResult { let selfInteger = BigUInt(self) let thatInteger = BigUInt(that) return selfInteger.map { thatInteger.map($0.compare(to:)) ?? .orderedAscending } ?? thatInteger.map { _ in .orderedDescending } // swiftformat:disable:this indent ?? compare(that, options: mask, range: range, locale: locale) // swiftformat:disable:this indent } } private extension [String] { func compareSemVerElements(to that: Self) -> ComparisonResult { zip(self, that).first { $0 != $1 }.map { $0.compareSemVerElement(to: $1) } ?? dropLast { $0 == "0" }.count.compare(to: that.dropLast { $0 == "0" }.count) // swiftformat:disable:this indent } } private extension Substring? { var elements: [String] { map { $0.split(separator: ".") }?.map(String.init(_:)) ?? [] } } extension Version { func compareSemVer(to that: Self) -> ComparisonResult { let coreComparison = coreElements.compareSemVerElements(to: that.coreElements) return coreComparison == .orderedSame ? prereleaseElements.compareSemVerElements(to: that.prereleaseElements) // swiftformat:disable:this indent : coreComparison } func compareSemVerAndBuild(to that: Self) -> ComparisonResult { let semVerComparison = compareSemVer(to: that) return semVerComparison == .orderedSame ? buildElements.compareSemVerElements(to: that.buildElements) // swiftformat:disable:this indent : semVerComparison } } private nonisolated(unsafe) let universalSemVerRegex = /([^-+]*+)?+(?:-([^+]*+))?+(?:\+(.*+))?+/ ================================================ FILE: Tests/MASTests/Commands/MASTests+Home.swift ================================================ // // MASTests+Home.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // private import ArgumentParser @testable private import mas internal import Testing private extension MASTests { @Test func `cannot find app home for unknown app ID`() async { let actual = await consequencesOf(try await MAS.main(try MAS.Home.parse(["1"])) { await $0.run(catalogApps: []) }) let expected = Consequences() #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Commands/MASTests+List.swift ================================================ // // MASTests+List.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // private import ArgumentParser @testable private import mas internal import Testing private extension MASTests { @Test func `lists apps`() { let actual = consequencesOf(try MAS.main(try MAS.List.parse([])) { $0.run(installedApps: []) }) let expected = Consequences( nil, "", """ Warning: No installed apps found If this is unexpected, any of the following command lines should fix things by reindexing apps in\ Spotlight (which might take some time): # Individual apps (if you know exactly what apps were incorrectly omitted): mdimport /Applications/Example.app # All apps ( is the volume optionally selected for large apps): mdimport /Applications /Volumes//Applications # All file system volumes (if neither aforementioned command solved the issue): sudo mdutil -Eai on """, ) #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Commands/MASTests+Lookup.swift ================================================ // // MASTests+Lookup.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // private import ArgumentParser @testable private import mas internal import Testing private extension MASTests { @Test func `cannot lookup app info for unknown app ID`() { let actual = consequencesOf(try MAS.main(try MAS.Lookup.parse(["1"])) { $0.run(catalogApps: []) }) let expected = Consequences() #expect(actual == expected) } @Test func `outputs app info`() { let actual = consequencesOf( try MAS.main(try MAS.Lookup.parse(["1"])) { command in command.run( catalogApps: [ CatalogApp( adamID: 1, appStorePageURLString: "https://awesome.app", fileSizeBytes: "1000000", formattedPrice: "$2.00", minimumOSVersion: "10.14", name: "Awesome App", releaseDate: "2019-01-07T18:53:13Z", sellerName: "Awesome Dev", version: "1.0", ), ], ) }, ) let expected = Consequences( nil, """ Awesome App 1.0 [$2.00] By: Awesome Dev Released: 2019-01-07 Minimum OS: 10.14 Size: 1 MB From: https://awesome.app """, ) #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Commands/MASTests+Search.swift ================================================ // // MASTests+Search.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // private import ArgumentParser @testable private import mas internal import Testing private extension MASTests { @Test func `searches for slack`() { let actual = consequencesOf( try MAS.main(try MAS.Search.parse(["slack"])) { command in try command.run(catalogApps: [CatalogApp(adamID: 1, name: "slack", version: "0.0")]) }, ) let expected = Consequences(nil, "1 slack (0.0)\n") #expect(actual == expected) } @Test func `cannot search for nonexistent app`() { let searchTerm = "nonexistent" let actual = consequencesOf(try MAS.main(try MAS.Search.parse([searchTerm])) { try $0.run(catalogApps: []) }) let expected = Consequences(nil, "", "Error: \(MASError.noCatalogAppsFound(for: searchTerm))\n") #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Commands/MASTests+Seller.swift ================================================ // // MASTests+Seller.swift // mas // // Copyright © 2019 mas-cli. All rights reserved. // private import ArgumentParser @testable private import mas internal import Testing private extension MASTests { @Test func `cannot find seller URL for unknown app ID`() async { let actual = await consequencesOf(try await MAS.main(try MAS.Seller.parse(["1"])) { await $0.run(catalogApps: []) }) let expected = Consequences() #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Commands/MASTests+Version.swift ================================================ // // MASTests+Version.swift // mas // // Copyright © 2018 mas-cli. All rights reserved. // private import ArgumentParser @testable private import mas internal import Testing private extension MASTests { @Test func `outputs version`() { let actual = consequencesOf(try MAS.main(try MAS.Version.parse([]))) let expected = Consequences(nil, "\(MAS.version)\n") #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Controllers/MASTests+CatalogApp+ITunesSearch.swift ================================================ // // MASTests+CatalogApp+ITunesSearch.swift // mas // // Copyright © 2019 mas-cli. All rights reserved. // private import Foundation @testable private import mas internal import Testing private extension MASTests { @Test func `iTunes searches for slack`() async { let actual = await consequencesOf( try await search(for: "slack") { _ in try (Data(fromResource: "slack"), URLResponse()) }.count, ) let expected = Consequences(39) #expect(actual == expected) } @Test func `looks up slack`() async { let adamID = 803_453_959 as ADAMID let actual = await consequencesOf( try await lookup(appID: .adamID(adamID)) { _ in try (Data(fromResource: "slack-lookup"), URLResponse()) }, ) #expect(actual.error == nil && actual.stdout.isEmpty && actual.stderr.isEmpty) guard let catalogApp = actual.value else { #expect(actual.value != nil) return } #expect( catalogApp.adamID == adamID // swiftformat:disable indent && catalogApp.appStorePageURLString == "https://apps.apple.com/us/app/slack-for-desktop/id803453959?mt=12" && catalogApp.name == "Slack" && catalogApp.sellerName == "Slack Technologies, Inc." && catalogApp.sellerURLString == "https://slack.com" && catalogApp.version == "3.3.3", ) // swiftformat:enable indent } } ================================================ FILE: Tests/MASTests/Extensions/Data.swift ================================================ // // Data.swift // mas // // Copyright © 2019 mas-cli. All rights reserved. // private import Foundation @testable private import mas extension Data { /// Unsafe initializer for loading data from string paths. /// /// - Parameters: /// - resourcePath: Relative path of resource within subfolderPath /// - ext: Extension of the resource /// - subfolderPath: Relative path of folder within the module /// - Throws: An `Error` if any problem occurs. init( fromResource resourcePath: String?, withExtension ext: String? = "json", inSubfolderPath subfolderPath: String? = "", ) throws { guard let resourceURL = Bundle.module.url(forResource: resourcePath, withExtension: ext, subdirectory: subfolderPath) else { throw MASError.error( """ Failed to find resource\ \(resourcePath.map { " at \($0)" } ?? "")\ \(ext.map { " with extension \($0)" } ?? "")\ \(subfolderPath.map { " in subfolder \($0)" } ?? "") """, ) } try self.init(contentsOf: resourceURL, options: .mappedIfSafe) } } ================================================ FILE: Tests/MASTests/MASTests.swift ================================================ // // MASTests.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // @testable private import mas internal import Testing @Suite(.serialized) struct MASTests { init() { MAS.printer.resetErrorCount() } } ================================================ FILE: Tests/MASTests/Models/MASTests+CatalogApp.swift ================================================ // // MASTests+CatalogApp.swift // mas // // Copyright © 2020 mas-cli. All rights reserved. // private import Foundation @testable private import mas internal import Testing private extension MASTests { @Test func `parses catalog app from things that go bump JSON`() { let actual = consequencesOf( try JSONDecoder().decode(CatalogApp.self, from: .init(fromResource: "things-lookup")).adamID, ) let expected = Consequences(1_472_954_003 as ADAMID) #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Models/MASTests+CatalogAppResults.swift ================================================ // // MASTests+CatalogAppResults.swift // mas // // Copyright © 2025 mas-cli. All rights reserved. // private import Foundation @testable private import mas internal import Testing private extension MASTests { @Test func `parses catalog app results from BBEdit JSON`() { let actual = consequencesOf(try JSONDecoder().decode(CatalogAppResults.self, from: .init(fromResource: "bbedit")).resultCount) let expected = Consequences(1) #expect(actual == expected) } @Test func `parses catalog app results from Things JSON`() { let actual = consequencesOf(try JSONDecoder().decode(CatalogAppResults.self, from: .init(fromResource: "things")).resultCount) let expected = Consequences(12) #expect(actual == expected) } } ================================================ FILE: Tests/MASTests/Resources/bbedit.json ================================================ { "resultCount": 1, "results": [ { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/d6/4f/1d/d64f1d4d-7317-be28-2622-1c0e0281bf10/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/6c/c3/6e/6cc36e59-bb9d-d1c8-8826-63d6fd9e3190/mzl.khdxvhmp.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/b8/3c/d8/b83cd83c-bb37-7f2b-ac4b-ae2675e6ad4d/mzl.bnkikobm.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/6c/f4/4a/6cf44a64-636b-beeb-fa4f-f2c36783a449/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/66/f6/44/66f6442b-c49d-179a-c3d3-7e6dde251137/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/9b/7a/52/9b7a5236-b568-3468-244b-81d16191341e/mzl.fhamemzv.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/94/60/02/9460029f-0e79-e5e9-88db-55a11b732c40/mzl.avroidbj.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/df/ef/3b/dfef3b25-1c8d-9671-ae41-3b15ded00137/mzl.mtktnwpr.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/6e/1b/ea/6e1bea82-16be-a75d-21db-9c58db4fb605/mzl.oifmshpk.jpg/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/d5/47/b4/d547b4ba-bcb7-def3-fefc-e9f50529577e/mzl.ywkwmifv.jpg/800x500bb.jpg" ], "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/b9/5d/a8/b95da808-2eea-5edd-0715-b0e13fd43f44/source/512x512bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/bare-bones-software-inc/id396307685?mt=12&uo=4", "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/b9/5d/a8/b95da808-2eea-5edd-0715-b0e13fd43f44/source/60x60bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/b9/5d/a8/b95da808-2eea-5edd-0715-b0e13fd43f44/source/100x100bb.png", "kind": "mac-software", "trackId": 404009241, "trackName": "BBEdit", "primaryGenreName": "Developer Tools", "isVppDeviceBasedLicensingEnabled": true, "genreIds": [ "6026", "6007" ], "minimumOsVersion": "10.14", "releaseNotes": "BBEdit 13.1.3 is a recommended update for all customers. This version includes an assortment of fixes for customer-reported issues, including a bug which caused the Search menu to display incorrect information or crash the application when pulled down. The full change notes are too long to include in the space allowed, but are available via the \"Support\" link on BBEdit's page in the App Store app.\n\nBBEdit 13.1 introduced significant feature additions and refinements, including (but not limited to): improved HTML previewing; advanced control over Markdown rendering; a new “Run Unix Command” command; significant improvements to its built-in CSS syntax coloring and navigation support; and built-in HTML Tidy commands.", "releaseDate": "2011-01-05T08:23:40Z", "formattedPrice": "Free", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "15198547", "sellerUrl": "https://www.barebones.com/products/bbedit/", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "trackContentRating": "4+", "currentVersionReleaseDate": "2020-07-30T12:53:37Z", "trackCensoredName": "BBEdit", "trackViewUrl": "https://apps.apple.com/us/app/bbedit/id404009241?mt=12&uo=4", "contentAdvisoryRating": "4+", "averageUserRating": 0, "version": "13.1.3", "wrapperType": "software", "primaryGenreId": 6026, "artistId": 396307685, "artistName": "Bare Bones Software, Inc.", "genres": [ "Developer Tools", "Productivity" ], "price": 0.00, "description": "BBEdit is the leading professional text, code, and markup editor for the Macintosh. \n\nAs the \"go to\" tool for web site designers, web application developers, writers, and software developers, this award-winning product provides an abundance of high-performance features for editing, searching, and manipulation of text, code, and HTML/XML markup.\n\nAs a replacement for TextWrangler, BBEdit is built by the same developers, using the same award-winning technology, and is identical to TextWrangler in every way you're used to. BBEdit is 64-bit and compatible with macOS Catalina.\n\nAn intelligent interface provides easy access to BBEdit’s best-of-class features, including: grep pattern matching; search and replace across multiple files; project definition tools; function navigation and syntax coloring for numerous source code languages; code folding; FTP and SFTP open and save; AppleScript and Automator support; Unix scripting support; text and code completion; a complete set of robust HTML tools; and more.\n\nBBEdit offers a 30-day evaluation period (beginning the first time you use it on your computer), during which its full feature set is available.\n\nDuring the evaluation period, BBEdit is fully functional. After the evaluation period has expired, you can continue to use BBEdit for free, forever, with no nag screens or unsolicited interruptions.\n\nIn “free mode”, BBEdit provides a modified set of features, which incorporates a powerful set of core features. Using BBEdit in free mode costs you nothing, while providing an upgrade path to advanced features and capabilities.\n\nTo enable BBEdit’s advanced features after the evaluation period is over, you will need to have an active BBEdit subscription. Subscriptions are available on either a monthly or annual basis.\n\nAn active subscription gives you access to all of BBEdit’s advanced features, including any new features that we introduce during updates or major upgrades, for as long as the subscription is in good standing.\n\nPlease see our comparison chart for a detailed listing of which advanced features are available with a subscription.\n\nSubscription terms and conditions:\n\nWe do not collect any data from your use of BBEdit, whether or not a subscription is in effect.\n\nYour interactions with BBEdit and with Bare Bones Software, Inc. are protected by our privacy policy.\n\nYour use of BBEdit is governed by the terms of its end-user license.\n\nWhen you purchase a subscription, payment will be charged to your iTunes Account upon confirmation of purchase.\n\nYour subscription will renew automatically, unless you cancel your subscription at least 24 hours prior to the end of the currently active subscription period. Your iTunes Account will be charged for the renewal within 24 hours prior to the end of the currently active subscription period, and your account history will reflect the cost of the renewal.\n\nYou can manage your subscription and cancel automatic renewal by going to your \"Manage my Subscriptions\" page after purchase.\n\nPurchasing a subscription will permanently end your evaluation period, and forfeits any unused portion of the evaluation period, if applicable.\n\nFor the full text of the BBEdit end user license for Mac App Store customers, please visit this page on our web site: https://www.barebones.com/products/bbedit/appstore/terms.html", "sellerName": "Bare Bones Software, Inc.", "currency": "USD", "bundleId": "com.barebones.bbedit", "userRatingCount": 0 } ] } ================================================ FILE: Tests/MASTests/Resources/slack-lookup.json ================================================ { "resultCount": 1, "results": [ { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/8d/07/74/8d0774c5-90aa-611c-3701-35f6158fb77e/source/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/6d/86/a7/6d86a74d-5c45-1a61-7828-ff3251360271/source/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/18/c1/38/18c138de-5bf2-586b-34de-c81e05568ea3/source/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple118/v4/4a/92/50/4a925081-61d0-ff32-eb2a-4b57db03aaab/source/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple128/v4/84/94/74/84947420-25dc-35e2-2761-9fa2b583c0a5/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple128/v4/84/94/74/84947420-25dc-35e2-2761-9fa2b583c0a5/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple128/v4/84/94/74/84947420-25dc-35e2-2761-9fa2b583c0a5/source/100x100bb.png", "artistViewUrl": "https://itunes.apple.com/us/developer/slack-technologies-inc/id453420243?mt=12&uo=4", "kind": "mac-software", "averageUserRatingForCurrentVersion": 4, "trackCensoredName": "Slack", "languageCodesISO2A": [ "EN", "FR", "DE", "JA", "ES" ], "fileSizeBytes": "74398324", "sellerUrl": "https://slack.com", "contentAdvisoryRating": "4+", "userRatingCountForCurrentVersion": 106, "trackViewUrl": "https://apps.apple.com/us/app/slack-for-desktop/id803453959?mt=12", "trackContentRating": "4+", "releaseNotes": "All updates are important, of course. This one contains security updates, and as we know, they’re the most important kind of all.", "currentVersionReleaseDate": "2018-10-02T23:28:05Z", "isVppDeviceBasedLicensingEnabled": true, "sellerName": "Slack Technologies, Inc.", "primaryGenreId": 12001, "primaryGenreName": "Business", "genreIds": [ "12001", "12014" ], "wrapperType": "software", "version": "3.3.3", "releaseDate": "2014-01-23T02:46:20Z", "minimumOsVersion": "10.9", "artistId": 453420243, "artistName": "Slack Technologies, Inc.", "genres": [ "Business", "Productivity" ], "price": 0, "currency": "USD", "description": "Slack brings team communication and collaboration into one place so you can get more work done, whether you belong to a large enterprise or a small business. Check off your to-do list and move your projects forward by bringing the right people, conversations, tools, and information you need together. Slack is available on any device, so you can find and access your team and your work, whether you’re at your desk or on the go.\n\nUse Slack to: \n• Communicate with your team and organize your conversations by topics, projects, or anything else that matters to your work\n• Message or call any person or group within your team\n• Share and edit documents and collaborate with the right people all in Slack \n• Integrate into your workflow, the tools and services you already use including Google Drive, Salesforce, Dropbox, Asana, Twitter, Zendesk, and more\n• Easily search a central knowledge base that automatically indexes and archives your team’s past conversations and files\n• Customize your notifications so you stay focused on what matters\n\nScientifically proven (or at least rumored) to make your working life simpler, more pleasant, and more productive. We hope you’ll give Slack a try.\n\nStop by and learn more at: https://slack.com/", "bundleId": "com.tinyspeck.slackmacgap", "trackId": 803453959, "trackName": "Slack", "formattedPrice": "Free", "userRatingCount": 1467, "averageUserRating": 4 } ] } ================================================ FILE: Tests/MASTests/Resources/slack.json ================================================ { "resultCount": 39, "results": [ { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/b6/96/82/b6968256-1ba7-017a-0027-58b8ba76af25/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/48/be/b8/48beb83d-91db-e956-edcd-ba44e5639a40/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/f5/bd/86/f5bd862a-ff6b-b1f7-d7da-149d7de82614/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/7a/75/8b/7a758b1c-7359-26fb-e5d2-1b7971589321/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/1d/d5/9d/1dd59dc1-8537-9471-d617-f0c4d80be440/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/1d/d5/9d/1dd59dc1-8537-9471-d617-f0c4d80be440/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/1d/d5/9d/1dd59dc1-8537-9471-d617-f0c4d80be440/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/slack-technologies-inc/id453420243?mt=12&uo=4", "kind": "mac-software", "trackId": 803453959, "trackName": "Slack", "releaseDate": "2014-01-23T02:46:20Z", "genreIds": [ "6000", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-08-07T16:58:01Z", "releaseNotes": "Bug Fixes\n• In rare cases, Slack would simply fail to start altogether. While we’re sure we could all use a coffee break, we know you could also use an application that runs. So we fixed that.\n• We made the screen you see when you have connection problems more useful. We also spruced it up a bit with a fresh coat of paint.\n• Our notifications system sprung a leak, so we brought it in for a tune-up. Everything should be running smoother now.", "primaryGenreId": 6000, "sellerName": "Slack Technologies, Inc.", "currency": "USD", "trackCensoredName": "Slack", "languageCodesISO2A": [ "EN", "FR", "DE", "JA", "PT", "ES" ], "fileSizeBytes": "80596748", "sellerUrl": "https://slack.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/slack/id803453959?mt=12&uo=4", "trackContentRating": "4+", "description": "Slack brings team communication and collaboration into one place so you can get more work done, whether you belong to a large enterprise or a small business. Check off your to-do list and move your projects forward by bringing the right people, conversations, tools, and information you need together. Slack is available on any device, so you can find and access your team and your work, whether you’re at your desk or on the go.\n\nUse Slack to: \n• Communicate with your team and organize your conversations by topics, projects, or anything else that matters to your work\n• Message or call any person or group within your team\n• Share and edit documents and collaborate with the right people all in Slack \n• Integrate into your workflow, the tools and services you already use including Google Drive, Salesforce, Dropbox, Asana, Twitter, Zendesk, and more\n• Easily search a central knowledge base that automatically indexes and archives your team’s past conversations and files\n• Customize your notifications so you stay focused on what matters\n\nScientifically proven (or at least rumored) to make your working life simpler, more pleasant, and more productive. We hope you’ll give Slack a try.\n\nStop by and learn more at: https://slack.com/", "genres": [ "Business", "Productivity" ], "artistId": 453420243, "artistName": "Slack Technologies, Inc.", "price": 0.00, "bundleId": "com.tinyspeck.slackmacgap", "version": "4.8.0", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/59/9f/b0/599fb00a-794b-2e98-4f8a-8c97e338bed7/mzl.oyxmnyla.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/7f/19/48/7f194831-3555-3def-ae86-a2c77966a12f/mzl.zfzeegwv.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/df/3e/d6/df3ed640-c00f-9b7e-577e-4eea3a36fc94/mzl.ldwdcyjg.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/28/b0/89/28b089ac-da7b-3cd4-66ea-a15dad33a52a/mzl.kbgzprvj.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/fd/3e/8e/fd3e8e76-ec96-c24a-3617-c6e5dd58fe14/mzl.slrwmdjl.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/c8/8c/0e/c88c0e70-cd58-a5fb-a222-35964b660627/mzl.wtqirxur.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/14/a9/20/14a920a6-836e-8213-4557-ed45f2f44042/mzl.keqdwues.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/26/38/45/2638454f-1887-639c-750e-9c6d60d9b9c1/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/03/2b/f6/032bf675-9f4e-1c8f-422b-27a13f1905e4/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/81/86/1f/81861f8c-689a-fd6a-79c0-277817e0b7f2/mzl.zdmmurjb.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/99/82/0b/99820b2f-94cb-c950-ae6d-7f0c2398570c/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/99/82/0b/99820b2f-94cb-c950-ae6d-7f0c2398570c/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/99/82/0b/99820b2f-94cb-c950-ae6d-7f0c2398570c/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/readdle-inc/id285035419?mt=12&uo=4", "kind": "mac-software", "trackId": 1176895641, "trackName": "Spark – Email App by Readdle", "releaseDate": "2016-11-30T18:24:05Z", "genreIds": [ "6007", "6002" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.13", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2020-09-02T11:52:29Z", "releaseNotes": "Today's update improves Spark's experience with Microsoft 365 and contains a small fix for the issue with the cursor in the 'say something' field. \n\nIf you feel generous and have a couple of minutes, please leave your review. It makes a difference to us. Thank you in advance. :) \nAnd if you need us you can find us at support@sparkmailapp.com", "primaryGenreId": 6007, "sellerName": "Readdle Inc.", "currency": "USD", "trackCensoredName": "Spark – Email App by Readdle", "languageCodesISO2A": [ "EN", "FR", "DE", "IT", "JA", "PT", "RU", "ZH", "ES" ], "fileSizeBytes": "50692261", "sellerUrl": "https://sparkmailapp.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/spark-email-app-by-readdle/id1176895641?mt=12&uo=4", "trackContentRating": "4+", "description": "Spark is the best personal email client and a revolutionary email for teams. You will love your email again! \n\n\"Best of the App Store\" - Apple\n\"It's a combination of polish, simplicity, and depth\" - FastCompany\n\"You can create an email experience that works for you\" - TechCrunch\n\n**Beautiful and Intelligent Email App**\nWe are building the future of email. Modern design, fast, intuitive, collaborative, seeing what’s important, automation and truly personal experience that you love - this is what Spark stands for.\n\n**Farewell to Busy Inbox**\nSmart Inbox lets you quickly see what's important in your inbox and clean up the rest. All new emails are smartly categorized into Personal, Notifications and Newsletters.\n\n**Discuss email privately**\nInvite teammates to discuss specific emails and threads. Ask questions, get answers, and keep everyone in the loop.\n\n**Create email together**\nFor the first time ever, collaborate with your teammates using real-time editor to compose professional emails.\n\n**Schedule emails to be sent later**\nSchedule emails to be sent when your recipient is most likely to read them. It works even if your device is turned off.\n\n**Snooze That One For Later**\nSnooze an email and get back to it when the time is right. Snoozing works across all your Apple devices.\n\n**Find Any Email In An Instant**\nPowerful, natural language search makes it easy to find that email you're looking for. Just search the way you think and let Spark do the rest.\n\n**Get Notified About Important Emails Only**\nSmart Notifications filter out the noise, letting you know when an email is important, saving you from notification overload.\n\n**Powerful Integrations**\nIntegrate Spark into your workflow and take productivity to the next level. Supports Dropbox, Box, iCloud Drive, and more.\n\n**Built-in calendar**\nA full-featured calendar works right in your email to help you always be on top of your schedule. Create events easily using natural language.\n\n**Create links to email**\nCreate secure links to a specific email or conversation. Share the link on Slack, Skype, CRM, or any other medium so your team can see it and collaborate around it.\n\n**Sign Off With A Swipe**\nBefore you send an email, quickly swipe to choose the right signature for the occasion.\n\n**Email with Emotion**\nQuick Replies get the point across with just a tap. Love, like or acknowledge an email in an instant.\n\n**Email Never Looked This Good**\nThat terrible mess in your inbox is now replaced it with a beautiful, threaded message design.\n\n**A Truly Personal Experience**\nCustomize Spark to work as you do. You decide which swipes do what, what cards are shown, and how many emails you want to see.\nYou’ll love your email again!\n \nIf you need us, you can always find us at rdsupport@readdle.com", "genres": [ "Productivity", "Utilities" ], "artistId": 285035419, "artistName": "Readdle Inc.", "price": 0.00, "bundleId": "com.readdle.smartemail-Mac", "version": "2.8.3", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple111/v4/8c/8b/f8/8c8bf8af-a7ef-eebf-3359-bfca1c8ab7d6/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple91/v4/e2/a4/23/e2a42353-e964-59dd-f578-6eaf7e6db808/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is2-ssl.mzstatic.com/image/thumb/Purple62/v4/6b/cf/1e/6bcf1e4b-4c74-35d4-c626-9207dfe3ff7f/source/60x60bb.png", "artworkUrl512": "https://is2-ssl.mzstatic.com/image/thumb/Purple62/v4/6b/cf/1e/6bcf1e4b-4c74-35d4-c626-9207dfe3ff7f/source/512x512bb.png", "artworkUrl100": "https://is2-ssl.mzstatic.com/image/thumb/Purple62/v4/6b/cf/1e/6bcf1e4b-4c74-35d4-c626-9207dfe3ff7f/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/tenor/id917932199?mt=12&uo=4", "kind": "mac-software", "trackId": 1043270657, "trackName": "GIF Keyboard", "releaseDate": "2015-10-29T04:29:03Z", "genreIds": [ "6002", "6005" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.11", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2018-03-17T21:10:03Z", "releaseNotes": "- We've updated our Terms of Service and Privacy Policy. If you have a Tenor account, please make sure to accept the new terms.\n- Tenor GIF Keyboard has been optimized -- the app should now run more smoothly than ever.", "primaryGenreId": 6002, "sellerName": "Tenor, Inc.", "currency": "USD", "trackCensoredName": "GIF Keyboard", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "4774516", "sellerUrl": "https://tenor.com", "contentAdvisoryRating": "17+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/gif-keyboard/id1043270657?mt=12&uo=4", "trackContentRating": "17+", "description": "Sometimes emojis just don’t cut it. Share your true feelings with GIFs.\n\nDrag and drop GIFs from your menubar directly into iMessage, and collect your favorite GIFs from anywhere by dropping GIFs into your menubar.\n\nIt’s perfect for all those times you wanted to...\n\n- Express just how much you love pizza.\n- Annoy your friends with that one lyric you just can’t stop singing.\n- Tell your crush that cuddling should be in your future. \n\n\nTenor GIF for Mac Features:\n\n* Always send the perfect GIF and video responses to your best friends straight from your menubar! \n\n* Browse through categories like reactions, music, trending and more. \n\n* Search millions of GIFs and videos on Tenor for the perfect moment. You can also tap the emoji icon in the menu to search by your favorite emoji!\n\n* See a GIF you like while browsing the web or receive a GIF from a friend? Drag/Drop the GIF to your Menubar to save for later. That means no saving to clunky desktop folders, and its instantly available from your toolbar\n\n* Works with your favorite messengers and social networks including iMessage, Slack, Email, Telegram, Facebook, and Reddit\n\n* Syncs with Tenor GIF Keyboard on your iPhone. Your GIF collections are instantly available on both your iPhone and Mac so your favorites GIFs are accessible everywhere", "genres": [ "Utilities", "Social Networking" ], "artistId": 917932199, "artistName": "Tenor", "price": 0.00, "bundleId": "com.riffsy.GIF-for-Mac", "version": "2.0.5", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple115/v4/dd/20/f0/dd20f0d6-79d5-3fcc-5a09-fe8a110cdc16/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple115/v4/58/1d/cb/581dcbf0-9709-1efd-cdd2-b7bb065680ae/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple125/v4/b4/36/3c/b4363cd8-2a99-4d0c-6c50-e54fae209938/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple125/v4/ef/63/79/ef63795c-1dea-c772-b5e3-3a2d4dfadfc9/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple115/v4/fc/0c/66/fc0c66b9-4c1c-d08b-dfdb-62f3e10710a5/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/4e/a2/52/4ea25263-c63c-5073-8f3c-c907bbe7b332/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/4e/a2/52/4ea25263-c63c-5073-8f3c-c907bbe7b332/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/4e/a2/52/4ea25263-c63c-5073-8f3c-c907bbe7b332/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/moxtra-inc/id551221476?mt=12&uo=4", "kind": "mac-software", "trackId": 991215717, "trackName": "Moxtra: Business Collaboration", "releaseDate": "2015-05-13T01:51:40Z", "genreIds": [ "6000", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.8", "primaryGenreName": "Business", "currentVersionReleaseDate": "2019-03-20T23:26:06Z", "releaseNotes": "- Fixed bugs", "primaryGenreId": 6000, "sellerName": "Moxtra, Inc.", "currency": "USD", "trackCensoredName": "Moxtra: Business Collaboration", "languageCodesISO2A": [ "DA", "NL", "EN", "FI", "FR", "DE", "ID", "IT", "JA", "KO", "PT", "RU", "ZH", "ES", "SV", "TH", "ZH", "TR", "VI" ], "fileSizeBytes": "13535823", "sellerUrl": "https://www.moxo.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/moxtra-business-collaboration/id991215717?mt=12&uo=4", "trackContentRating": "4+", "description": "Moxtra: Accelerating business in the mobile world.\n\nMoxtra is a collaboration solution built to accelerate business. Present, secure feedback, get approvals on content to close business while on the go. Collaborate on documents and content across teams, with customers, partners, and colleagues. Recreate the power of face-to-face meetings with secure messaging, robust document collaboration, video conferencing, electronic signature, and more – in context. Moxtra is an secure, enterprise class service available as a white-label, private cloud, or on-premise solution.", "genres": [ "Business", "Productivity" ], "artistId": 551221476, "artistName": "Moxtra, Inc.", "price": 0.00, "bundleId": "com.moxtra.desktop", "version": "6.0.7", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/9e/fd/73/9efd7364-ffaf-5e20-e1b4-59b036f97cd9/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/a9/8c/4b/a98c4be2-9027-1006-6528-c5dfe1ed885d/mzl.ltakrafq.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/80/e9/f2/80e9f22e-d740-363f-1830-d318f2200984/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/80/e9/f2/80e9f22e-d740-363f-1830-d318f2200984/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/80/e9/f2/80e9f22e-d740-363f-1830-d318f2200984/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/henrik-wenz/id1456462086?mt=12&uo=4", "kind": "mac-software", "trackId": 1456462087, "trackName": "All-in-One Messenger", "releaseDate": "2019-03-28T00:50:45Z", "genreIds": [ "6007", "6005" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2019-04-09T02:15:29Z", "releaseNotes": "- Frameless window \n- Better design\n- Fixed messenger load bug\n- Use custom protocol (improve security)", "primaryGenreId": 6007, "sellerName": "Henrik Wenz", "currency": "USD", "trackCensoredName": "All-in-One Messenger", "languageCodesISO2A": [ "AM", "AR", "BN", "BG", "CA", "HR", "CS", "DA", "NL", "EN", "ET", "FI", "FR", "DE", "EL", "GU", "HE", "HI", "HU", "ID", "IT", "JA", "KN", "KO", "LV", "LT", "MS", "ML", "MR", "NB", "FA", "PL", "PT", "RO", "RU", "SR", "ZH", "SK", "SL", "ES", "SW", "SV", "TA", "TE", "TH", "ZH", "TR", "UK", "VI" ], "fileSizeBytes": "59177570", "sellerUrl": "https://allinone.im/", "contentAdvisoryRating": "17+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/all-in-one-messenger/id1456462087?mt=12&uo=4", "trackContentRating": "17+", "description": "Use messengers like WhatsApp, Telegram, Messenger, Skype and many more in one app.", "genres": [ "Productivity", "Social Networking" ], "artistId": 1456462086, "artistName": "Henrik Wenz", "price": 0.00, "bundleId": "im.allinone.messenger", "version": "0.3.1", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/7d/31/f4/7d31f44c-5094-b4c8-d630-f218f493058c/mzl.jkthnpaz.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/b3/61/d9/b361d9f7-881e-3692-aac2-b28be0a30ae1/mzl.eznpdmta.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/d7/3b/96/d73b96f0-2c4e-0802-d46a-f30b73db8f2e/mzl.xafzbvqs.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/41/ee/eb/41eeebe8-f759-d786-a952-098d251a3274/mzl.pzxvxjgw.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/a3/9a/fe/a39afe75-0d3f-6445-3c5e-373653a174c8/mzl.lcubnpym.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/f1/f4/2d/f1f42ded-7f57-dffc-961d-5cdde8ea3f7f/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/6e/20/de/6e20de36-06ea-8f38-777d-9b37e6877859/mzl.ydczebqy.png/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/84/a2/81/84a28150-6acf-171d-fd2d-a4581e94ba2c/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/84/a2/81/84a28150-6acf-171d-fd2d-a4581e94ba2c/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/84/a2/81/84a28150-6acf-171d-fd2d-a4581e94ba2c/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/riva-fzc/id503815096?mt=12&uo=4", "kind": "mac-software", "trackId": 883594849, "trackName": "Flock: Team Communication App", "releaseDate": "2014-06-05T23:27:57Z", "genreIds": [ "6000", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-05-18T12:00:43Z", "releaseNotes": "We've added animated stickers! \nWe also squashed some bugs and improved overall performance of the app!\n\nFlock as you were!", "primaryGenreId": 6000, "sellerName": "RIVA FZC", "currency": "USD", "trackCensoredName": "Flock: Team Communication App", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "84779228", "sellerUrl": "https://flock.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/flock-team-communication-app/id883594849?mt=12&uo=4", "trackContentRating": "4+", "description": "Flock is a powerful business messaging and team collaboration app that brings all your work in one place. \n\nToday, your team’s communication is scattered across emails, meetings, and multiple tools. With Flock you can quickly bring people together, discuss ideas, share information, assign tasks and track team progress, so your team can focus on what they do best. Whether you are a large enterprise, a small business or a high-growth startup, Flock effortlessly adapts to your unique needs. \n\nOver 32,000 organizations love Flock and here’s why you might find it useful too:\n• Team communication made easy through 1-1 chats and group messaging\n• Organize conversations into different channels for projects, departments or topics\n• Find information and share files on-the-go\n• Enjoy video & audio calling along with free screen sharing capabilities\n• Do more with free built-in productivity tools such as to-do’s, reminders and polls\n• Integrates with all your favourite tools and services that you already use including Google \n Drive, Trello, Jira, GitHub, Hubspot, etc.\n• Effortlessly find information that was discussed in a 1-1/team chat using Search \n• Flock is private, safe and secure (We are SOC2 and GDPR compliant)\n\nFlock is free to use for as many users and for as long as you want. You can upgrade to our paid plans for enhanced features and increased user control.\n\nRecommended by experts:\n• Gartner Cool Vendor 2018\n• G2 Crowd Leader Spring 2018 \n• #1 Slack Alternative by PCMag \n• Top rated alternative for Slack by ProductHunt\n\nYou’re in good company.\n- Tim Hortons\n- Avendus\n- Gini and Jony\n- Ricoh\n- Victorinox\n\nAnd here’s what our happy users have to say about Flock:\n\n“I can’t say enough kind things about Flock. Moved the team to it from Slack and couldn’t be happier.”\n- Luke Rodriguez, Modern Horrors\n\n“Flock is convenient and real time and is making communication seamless and easy. My entire team today is on Flock.”\n- Prashant Tandon, CEO and Co-Founder, 1MG\n\n“Flock has become the way our Sales Team communicates. It's fast, reliable, fun and easy to use.”\n- Bryan Morales, CIB Corporation\n\nFollow us on Facebook and Twitter @flock", "genres": [ "Business", "Productivity" ], "artistId": 503815096, "artistName": "RIVA FZC", "price": 0.00, "bundleId": "to.go.osx", "version": "2.2.388", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/40/d6/69/40d6695a-6d55-a6af-2c5a-918baf218ec9/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/cd/1f/5e/cd1f5ef8-b7d9-c5da-3280-45c703d89ec8/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/17/44/1c/17441c02-37d6-d3f6-420b-da5114df9c45/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/6e/05/37/6e0537aa-3438-6bfe-d7d2-e02d30ee8d34/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/b6/24/cd/b624cd52-2079-5118-bea8-94f7fa31a467/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/e1/0b/8d/e10b8d63-660a-2828-3ecb-eb7a197b62fb/mzl.umozqbal.png/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/96/76/38/96763896-f145-a59b-8bd0-934a7dbce2a3/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/96/76/38/96763896-f145-a59b-8bd0-934a7dbce2a3/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/96/76/38/96763896-f145-a59b-8bd0-934a7dbce2a3/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/bloop-s-r-l/id389546852?mt=12&uo=4", "kind": "mac-software", "trackId": 918858936, "trackName": "Airmail 4", "releaseDate": "2014-10-16T20:48:01Z", "genreIds": [ "6007", "6002" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.12", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2020-08-04T22:49:54Z", "releaseNotes": "- Bugfix", "primaryGenreId": 6007, "sellerName": "Bloop S.R.L", "currency": "USD", "trackCensoredName": "Airmail 4", "languageCodesISO2A": [ "AR", "MY", "CA", "CS", "DA", "NL", "EN", "FI", "FR", "GL", "DE", "EL", "HE", "HU", "IT", "JA", "KO", "NB", "NN", "PL", "PT", "RO", "RU", "SR", "ZH", "SK", "ES", "SV", "ZH", "TR", "UK" ], "fileSizeBytes": "62121809", "sellerUrl": "https://airmailapp.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/airmail-4/id918858936?mt=12&uo=4", "trackContentRating": "4+", "description": "*** Apple Design Award 2017 Winner ***\n\nAirmail is a mail client designed with performance and intuitive interaction in mind optimized for macOS and iOS!\n\nSupport for iCloud™, MS Exchange, Gmail™, Google™ Apps, IMAP, POP3, Yahoo!™, AOL™, Outlook.com™, Live.com™\n\nAirmail was designed from the ground UP to retain the same experience with a single or multiple accounts and provide a quick, modern user experience. Airmail is clean and allows you to get to your emails without interruption - it's the mail client for the 21st century. \n\nWe have taken usability and function to the next level with Airmail and bring a striking-design with support for all major email services. Switch between accounts like a breeze and quick reply to incoming messages within seconds - email has never been so easy and productive. \n\n- iCloud Account sync\n- Handoff support \n- Today Extension, quick access to your inbox.\n- Action Extension, Airmail Compose, Airmail Share.\n\nAccounting:\n- Unified Inbox\n- Alias with custom SMTP\n- Exchange, iCloud™, Gmail™, IMAP, POP3, Google™ Apps, Yahoo!™, AOL™, Outlook.com™, Live.com™\n- Local Accounts\n- Import from Apple Mail, MBOX archive, EML, EMLX\n\nInteractions:\n- Quick Reply\n- Offline operations\n- Move messages on Different mailboxes\n- Multi Touch gestures\n\nShortcuts:\n- Gmail Shortcuts\n- Custom global shortcuts\n- Quick Label, Move, Label and Archive\n- Quick folder selection\n\nConversations:\n- Group by id\n- Group by subject\n- Chronological Reverse \n- Muted CC’d conversation\n\nVisual:\n- Multiple visual themes\n- Minimal and extended Mode\n- Plain Text Rendering\n\nNotifications:\n- Per account notifications\n- Notification center support\n- Notification alerts with custom actions\n\nAddress:\n- Gmail, Exchange and OS X contacts\n- Contacts Group\n- Filter by address\n- Open Directory search\n\nComposer:\n- Exchange Global Address List\n- Markdown, Html(Html Source for templates), and text only.\n- Bullet and numbered list.\n\nSending:\n- Auto CC, BCC.\n- Send Delay\n- Redirect\n- Send again \n- Bounce\n\nAdvanced:\n- EML Import/Export\n- Applescript\n\nSignatures:\n- Multiple Account Signatures\n- Markdown, Rich text, Html Source, and text only.\n- Signature above or below the quote\n\nSearch & Filtering:\n- Global search for multiple accounts\n- Realtime powerful filters\n- Sort messages, Date, Attachments, Conversations ....\n- Show messages of the same user\n- Flags and filters\n\nFolders, Labels:\n- Nested folders\n- Create, Delete and edit folders\n- Custom colors synced by iCloud\n- Custom folder mappings\n- ToDo, Done, Memo\n\nSend to:\n- Omnifocus\n- Fantastical\n- Evernote\n- Apple Reminder\n- Calendar\n- BusyCal\n- Things\n- 2To\n- Wunderlist \n- Todoist\n\nAirmail 4 include Airmail Pro Subscription.\n\nAirmail Pro Features:\n- Access to iPhone and iPad\n- New Design\n- Smart box\n- New Rendering\n- New Search\n- New Themes\n- Custom Actions\n- Customizable Layout\n- Live Support\n\nNotes:\n• Airmail Pro is free for all users that are subscribed to Airmail Pro for iOS or have purchased Airmail 3 since 1st January 2019.\n• Previous users can still use Airmail with all the features they have purchased for under Preferences>General> Airmail Legacy.\n• New Users can try Airmail without Multi Account, and limited capabilities.\n\n$2.99 Monthly, $9.99 Yearly\n\n---\n\n• Price may vary by location and promotion.\n• Subscriptions will be charged to your credit card through your iTunes account. \n• Your subscription will automatically renew unless canceled at least 24 hours before the end of the current period.\n• Manage your subscriptions in Account Settings after purchase. \n• Any unused portion of a free trial peri​od, will be forfeited when the user purchases a subscription.\n• Account will be charged for renewal within 24-hours prior to the end of the current period, and identify the cost of the renewal\n\n\nPrivacy Policy: https://airmailapp.com/privacymac.html\nTerms of Service: https://airmailapp.com/eulamac.html", "genres": [ "Productivity", "Utilities" ], "artistId": 389546852, "artistName": "Bloop S.R.L.", "price": 0.00, "bundleId": "it.bloop.airmail2", "version": "4.1.6", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple118/v4/4f/fe/c7/4ffec732-9c5c-e861-4ebe-284d67391792/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple128/v4/3e/aa/5f/3eaa5f22-7306-9848-0aef-9c53013f9bd7/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple128/v4/f6/af/28/f6af2812-b83e-6c0e-c89c-197381ee1c3b/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple118/v4/59/e4/dc/59e4dc92-bede-17f4-2933-365f3d60244e/pr_source.jpg/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/de/ad/28/dead28bc-2e1f-b3f3-73a8-e970c1bfce6b/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/a0/68/f8/a068f85a-f72a-1efb-d0ee-0543fb97d48b/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/a0/68/f8/a068f85a-f72a-1efb-d0ee-0543fb97d48b/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/a0/68/f8/a068f85a-f72a-1efb-d0ee-0543fb97d48b/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/rocket-chat-technologies-corp/id1148477217?mt=12&uo=4", "kind": "mac-software", "trackId": 1086818840, "trackName": "Rocket.Chat", "releaseDate": "2016-03-09T18:02:58Z", "genreIds": [ "6007", "6002" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2020-03-13T00:15:30Z", "releaseNotes": "Improvements:\n- Update to Electron 7\n- Performance tweaks\n- Better handling of power events\n\nBug fixes:\n- Custom spell checking dictionaries\n- Screen sharing in Jitsi\n- TouchBar buttons\n- Embed dialogs\n- Notification avatars", "primaryGenreId": 6007, "sellerName": "Rocket.Chat Technologies Corp.", "currency": "USD", "trackCensoredName": "Rocket.Chat", "languageCodesISO2A": [ "AM", "AR", "BN", "BG", "CA", "HR", "CS", "DA", "NL", "EN", "ET", "FI", "FR", "DE", "EL", "GU", "HE", "HI", "HU", "ID", "IT", "JA", "KN", "KO", "LV", "LT", "MS", "ML", "MR", "NB", "FA", "PL", "PT", "RO", "RU", "SR", "ZH", "SK", "SL", "ES", "SW", "SV", "TA", "TE", "TH", "ZH", "TR", "UK", "VI" ], "fileSizeBytes": "69450749", "sellerUrl": "https://rocket.chat/", "contentAdvisoryRating": "17+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/rocket-chat/id1086818840?mt=12&uo=4", "trackContentRating": "17+", "description": "Rocket.Chat is a Web Chat Server, developed in JavaScript, using the Meteor full-stack framework. It is a great solution for communities and companies wanting to privately host their own chat service or for developers looking forward to building and evolve their own chat platforms. With Rocket.Chat, you can send a group message, send 1:1 message, share files, integrate with your favorite products, and more!\n\nKEY FEATURES:\n\n* Free Open Source Software\n* Hassle free MIT license\n* BYOS (bring your own server)\n* Multiple Rooms\n* Direct Messages\n* Private Groups\n* Public Channels\n* Desktop and Mobile Notifications\n* Edit and Delete Sent Messages\n* Mentions\n* Avatars\n* Markdown\n* Emojis\n* Transcripts / History\n* File Upload / Sharing\n* I18n - [Internationalization with Lingohub]\n* Hubot Friendly - [Hubot Integration Project]\n* Media Embeds\n* Link Previews\n* LDAP Authentication\n* REST-full APIs\n* Remote Locations Video Monitoring\n* Native Cross-Platform Desktop Application\n\nNEWS:\n\nFeatured on: Hacker News, Product Hunt, JavaScript Weekly, WWWhatsNew, ClasesDePeriodismo\n\nGET IT NOW:\n\n* Check out at https://rocket.chat\n* ONE-CLICK-DEPLOYMENT – See instruction on our GitHub repository", "genres": [ "Productivity", "Utilities" ], "artistId": 1148477217, "artistName": "Rocket.Chat Technologies Corp.", "price": 0.00, "bundleId": "chat.rocket", "version": "2.17.9", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple124/v4/8b/59/37/8b593789-a69e-9a22-9477-049f7466917e/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/4b/3a/f6/4b3af6d0-3448-2ee6-f649-fe6221e708bf/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/91/27/54/91275485-91b8-292e-f82b-829b14e9226d/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/e6/de/3f/e6de3ffa-624e-588f-1e1c-623df367f53b/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple124/v4/2a/93/0b/2a930bef-cbd3-1f03-35e4-6389d5f9d7c5/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/26/78/d9/2678d9e3-4b91-8d9c-7b59-5ebd51847d3d/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/ea/82/b9/ea82b92a-9e6e-6e59-b27b-e204d956fef3/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/ea/82/b9/ea82b92a-9e6e-6e59-b27b-e204d956fef3/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/ea/82/b9/ea82b92a-9e6e-6e59-b27b-e204d956fef3/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/tencent-technology-shenzhen-company-limited/id292374531?mt=12&uo=4", "kind": "mac-software", "trackId": 1189898970, "trackName": "WeCom-Business IM & Work Tools", "releaseDate": "2018-02-27T07:21:11Z", "genreIds": [ "6000", "6002" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.11", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-09-01T02:01:05Z", "releaseNotes": "I.Improved Multiple Productivity Tools\n1.WeDoc \n-Optimized question editing and added three new questions: Picture, File, Location and Date. You can support timed repeated collection.\n-A variety of templates are newly added to the template center based on different industries, and custom templates are supported. Users can save any template to the template center.\n-Added comment panel, where you can quickly browse all comments of documents.\n2.WeDrive\n-Added \"Preview only\" permission and watermark settings. You can preview files in mainstream formats. It is supported in WeDrive Pro.\n3.Optimized Event APIs. Companies can obtain events on the calendar created through APIs, allowing data sharing from the existing system.\n4.Live\n-Admins can customize the permission to initiate live video for externals. Only allowed members can initiate live videos for external customers and partners.\n\nII.Optimized Basic OA Apps\n1.Approvals\n-You can set the permissions for approvers to view or edit the approval application fields. In this way, you can hide content from the specified approvers or allow them to modify the application content.\n-When setting the approval process, you can copy the process from other templates.\n-The duration of leave, offsite, business trip, and overtime work can be modified and counted on a daily basis. When the duration is set to \"Working Days\", the overtime and leave duration can be calculated more accurately according to the punch rules for the applicant.", "primaryGenreId": 6000, "sellerName": "Tencent Technology (Shenzhen) Company Limited", "currency": "USD", "trackCensoredName": "WeCom-Business IM & Work Tools", "languageCodesISO2A": [ "EN", "ZH", "ZH" ], "fileSizeBytes": "87361336", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/wecom-business-im-work-tools/id1189898970?mt=12&uo=4", "trackContentRating": "4+", "description": "WeCom is a business communication and office collaboration tool developed by Tencent WeChat Team. WeCom provides the familiar communication experience as WeChat and connects with WeChat in an all-round manner. It also offers productivity tools such as Event, Meeting, WeDoc and WeDrive, and flexible OA apps for effective business communication and management. \nWeCom has been widely adopted by millions of leading organizations, including Rainbow, P&G, Cartier, WalMart, Chow Tai Fook, L'Oreal, IKEA, Bank of China, PICC, Deppon Express, and Changan Automobile.\n\n1. A familiar communication experience\n[Ease of Use] Provides a IM experience consistent with WeChat’s.\n[Reliable Storage] Enables real-time message syncing to PCs, mobiles phones, the cloud, and other devices.\n[Efficient Communication] Allows users to check message read status to ensure effective communication.\n[Corporate Directory] Allows administrators to batch import and manage corporate directory. Finding co-workers has never been easier.\n\n2. Connecting with WeChat\n[Exchange Messages] Add WeChat users as contacts and offer services via private or group chats.\n[Contact Customers] Companies can view and manage customers added by members, and assign customers of former members.\n\n3. Integrated with kinds of efficiency tool\n[Event Management] Simultaneously, through \"Make an Appointment\", you can easily check the idle/busy status of the group members and select the appropriate time to start the event. Members will receive the event invitation in their Event app.\n[Multi-person Meeting] Initiate and join online meetings anytime & anywhere, allowing sharing documents and screens among up to 25 participants, and providing some management features for hosts.\n[WeDoc] Online real-time collaborative docs and sheets. Edits can be updated in real-time, freeing collaborators from transferring the files with each other.\n[WeDrive] Create a shared space with colleagues to keep track of the latest version. Edits can be updated in real-time.Companies can manage all company files in one place and allow audit by members, ensuring company data security.\n[Business Mailbox] Send and receive business emails and forward to Group Chats if needed.\n\n4. Diversified Office Apps\n[Basic Office Apps] Preset ready-to-use office apps such as Attendance, Approvals, Reports, Announcement, and Forum.\n[Third-party Apps] Provide companies with high-quality third-party apps and hardware, covering mobile office, and other fields, as well as smart attendance, unlimited screen casting, and meeting television.\n[APIs] Provide various APIs, making it easier for you to integrate company apps.\n\n5. Strong security capabilities\n[all-round safeguard] Based on the integration of offense and defense capabilities of Tencent over the past 20 years, WeCom is the first domestic office product to pass SOC2Type2 auditing, and has obtained ISO27018, ISO20000, ISO27001, and national three-level certifications to provide reliable data security guarantee for companies.\n\nWeCom, Offering Every Enterprise their own WeChat", "genres": [ "Business", "Utilities" ], "artistId": 292374531, "artistName": "Tencent Technology (Shenzhen) Company Limited", "price": 0.00, "bundleId": "com.tencent.WeWorkMac", "version": "3.0.30", "wrapperType": "software", "userRatingCount": 0 }, { "supportedDevices": [ "iPadMini4Cellular-iPadMini4Cellular", "iPadPro-iPadPro", "iPhone11ProMax-iPhone11ProMax", "iPadSeventhGenCellular-iPadSeventhGenCellular", "iPhone5s-iPhone5s", "iPadSeventhGen-iPadSeventhGen", "MacDesktop-MacDesktop", "iPad611-iPad611", "iPadAir2Cellular-iPadAir2Cellular", "iPadMini4-iPadMini4", "iPadPro97-iPadPro97", "iPad74-iPad74", "iPadProFourthGenCellular-iPadProFourthGenCellular", "iPadAir-iPadAir", "iPad878-iPad878", "iPadAirCellular-iPadAirCellular", "iPodTouchSeventhGen-iPodTouchSeventhGen", "iPhone11Pro-iPhone11Pro", "iPadMini5-iPadMini5", "iPadMini3Cellular-iPadMini3Cellular", "iPhone6-iPhone6", "iPadMini3-iPadMini3", "iPad71-iPad71", "iPadProSecondGen-iPadProSecondGen", "iPadProSecondGenCellular-iPadProSecondGenCellular", "iPadAir2-iPadAir2", "iPhoneXR-iPhoneXR", "iPhoneSE-iPhoneSE", "iPadPro97Cellular-iPadPro97Cellular", "iPad856-iPad856", "iPadAir3-iPadAir3", "iPad834-iPad834", "iPad76-iPad76", "iPhone8Plus-iPhone8Plus", "iPadMiniRetinaCellular-iPadMiniRetinaCellular", "iPhone11-iPhone11", "iPadMiniRetina-iPadMiniRetina", "iPad75-iPad75", "iPadProCellular-iPadProCellular", "iPhone8-iPhone8", "iPhoneX-iPhoneX", "iPad812-iPad812", "iPhone7-iPhone7", "iPhone6sPlus-iPhone6sPlus", "iPhoneSESecondGen-iPhoneSESecondGen", "iPadAir3Cellular-iPadAir3Cellular", "iPhone6s-iPhone6s", "iPhone6Plus-iPhone6Plus", "iPad72-iPad72", "iPhoneXS-iPhoneXS", "iPhoneXSMax-iPhoneXSMax", "iPodTouchSixthGen-iPodTouchSixthGen", "iPad73-iPad73", "iPadMini5Cellular-iPadMini5Cellular", "iPad612-iPad612", "iPhone7Plus-iPhone7Plus", "iPadProFourthGen-iPadProFourthGen" ], "advisories": [], "isGameCenterEnabled": false, "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/f5/c7/3d/f5c73d36-4ce5-5d17-41c9-ea7fe465ca71/f3972f46-f6e9-4c09-a114-4d3c3c2c34b0_Main_iPhone_Plus.png/392x696bb.png", "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/d4/24/0d/d4240dda-e267-ce51-5e4f-7714a2ca5a7a/fabfeffa-a3c0-4651-9823-fc81519ec21c_Discord_iPhone_Plus.png/392x696bb.png", "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/9d/da/6f/9dda6f0a-2d3f-3b24-ccf3-ea0186a51324/0ff3934b-cd50-4b5c-8ab2-5a5385521508_Slack_iPhone_Plus.png/392x696bb.png" ], "ipadScreenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/cb/c6/7f/cbc67f3d-f4fd-44e2-e2ed-053dee565346/ffab9b81-c9c1-4eaf-8cb0-a6ae649725de_Main_-_iPads.png/576x768bb.png", "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/72/d1/be/72d1be98-b513-1bde-9660-3506068d99f0/52fee114-79d9-470d-999b-d677ac0cdac5_Discord_iPads.png/576x768bb.png", "https://is2-ssl.mzstatic.com/image/thumb/Purple124/v4/92/77/66/927766d7-d61a-0133-49ee-f38153335b99/4838fdf7-45a1-4ea1-a1be-cf4ce9e20172_Slack_iPads.png/576x768bb.png" ], "appletvScreenshotUrls": [], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/89/f3/1e/89f31e56-bedd-4f2c-d86b-f3bb775fd7d4/source/60x60bb.jpg", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/89/f3/1e/89f31e56-bedd-4f2c-d86b-f3bb775fd7d4/source/512x512bb.jpg", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/89/f3/1e/89f31e56-bedd-4f2c-d86b-f3bb775fd7d4/source/100x100bb.jpg", "artistViewUrl": "https://apps.apple.com/us/developer/peroxaan-studios-llc/id1492913322?uo=4", "features": [ "iosUniversal" ], "kind": "software", "trackId": 1492913323, "trackName": "Talon – Webhooks & More", "releaseDate": "2020-06-28T07:00:00Z", "genreIds": [ "6026", "6002" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "13.0", "primaryGenreName": "Developer Tools", "currentVersionReleaseDate": "2020-08-24T14:19:01Z", "releaseNotes": "Introducing Talon Pro. Talon Pro is an expansion on the current features of Talon. Talon Pro allows you to shorten links, and more! It's $1.99 USD - but more features will be coming to Talon Pro in later updates.\n\nAlso, we've added custom webhooks to the app! You can now run whatever POST Request you'd like.", "primaryGenreId": 6026, "sellerName": "Peroxaan Studios, LLC", "currency": "USD", "trackCensoredName": "Talon – Webhooks & More", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "27502592", "sellerUrl": "https://peroxaan.com/Talon", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 4.77777999999999991587174008600413799285888671875, "userRatingCountForCurrentVersion": 9, "averageUserRating": 4.77777999999999991587174008600413799285888671875, "trackViewUrl": "https://apps.apple.com/us/app/talon-webhooks-more/id1492913323?uo=4", "trackContentRating": "4+", "description": "Talon is a simple tool for users to use, manage, and work with Webhooks on Discord and Slack. It's very easy to use, and allows a larger audience to work with Webhooks in a simple way, without requiring you to know how to code. Talon gets rid of everything complex, and gives you a nice and easy to use Interface thats familiar to use. We built Talon to take advantage of the features that Discord and Slack offer. Talon allows you to save and copy your links inside of the app, so you don't have to constantly go back and forth just to use the App. Also with Talon, you can run POST Requests to any service, just in case Talon does not yet offer something you have in mind.\n\nFor even more features, you can purchase Talon Pro. Talon Pro is a one time purchase that allows you to shorten links with Talon - and gives you more customizability. Talon Pro will be growing more with future updates.\n\nWant to suggest something for a future Talon update? Contact us!\n\nNow, that sounds pretty cool and all - but this is just the beginning. Stay tuned for future updates that'll bring more features and functionality to Talon!", "genres": [ "Developer Tools", "Utilities" ], "artistId": 1492913322, "artistName": "Peroxaan Studios, LLC", "price": 0.00, "bundleId": "com.peroxaan.Talon", "version": "1.2", "wrapperType": "software", "userRatingCount": 9 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple118/v4/48/6d/7f/486d7fa8-fc1c-b537-9983-7c842da60afb/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/db/40/87/db40870b-a6b5-8952-ab98-acfa27f92166/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/5a/7d/1f/5a7d1f19-4bc4-7309-ae08-a4cce36fef8c/pr_source.jpg/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple118/v4/ac/b5/27/acb5278b-dcdb-e52b-62f8-c300013fabc3/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b2/98/67/b29867d9-2e76-8ca6-6a12-a1ea8bed2be5/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b2/98/67/b29867d9-2e76-8ca6-6a12-a1ea8bed2be5/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b2/98/67/b29867d9-2e76-8ca6-6a12-a1ea8bed2be5/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/shanghai-best-oray-information-technology-co-ltd/id551941462?mt=12&uo=4", "kind": "mac-software", "trackId": 1305707014, "trackName": "Pgy", "releaseDate": "2018-01-10T06:10:10Z", "genreIds": [ "6002", "6000" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.12", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2020-08-31T02:19:25Z", "releaseNotes": "Update : \n\n1. Optimize the interface and icons. \n\n2. Repair known bug", "primaryGenreId": 6002, "sellerName": "Shanghai best oray information s&t co.,ltd", "currency": "USD", "trackCensoredName": "Pgy", "languageCodesISO2A": [ "EN", "ZH" ], "fileSizeBytes": "2352150", "sellerUrl": "https://pgy.oray.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/pgy/id1305707014?mt=12&uo=4", "trackContentRating": "4+", "description": "Pgy is a fast to build off-site virtual local area network APP software. Using a new independent research and development of Internet technology, through the network, to bring users a convenient network experience. Through the dandelion can be achieved remote access to local area network under the file, online game and other functions; at the same time can help small and medium enterprises quickly open up the branch or store between the remote network, mobile office, file sharing, remote monitoring, private LAN structures and other functions.\n\nThe main function:\n1. Off-site file sharing: easy to achieve more documents, important data, files and other resource sharing, support anytime, anywhere browsing\n2. Remote real-time view video monitoring: support real-time understanding of the operation of the branch, at any time to remotely view the details of the operation of the branch, video surveillance\n3. Remote mobile office: connect work and life, easy access to the company's OA system, file server, etc., at home to visit the office computer\n\nInstructions:\nAn APP only one step will be able to achieve intelligent networking features:\n== different equipment [such as two mobile phones] at the same time download, install dandelion APP, landing the same oray account, that is, to complete the automatic network.\n\nDandelion, step by step to build a virtual local area network, from the horizon if the neighbors.\nMore games, please pay attention: www.oray.comDandelion is a APP that can quickly build virtual lans. Using the new self-developed network technology, it can penetrate the Intranet and bring convenient network experience to users. Through dandelion can realize remote access LAN files, online game play and other functions; At the same time, it can help small and medium-sized enterprises to quickly open the long-distance network between branches or stores, realizing mobile office, file sharing, remote monitoring, private LAN building and other functions. \n\nMain functions: \n1. To realize file sharing in different places: to easily realize multiple documents, important data, files and other resources to share, and to support browsing each other anytime and anywhere \n2. Remote real-time view of video monitoring: support real-time understanding of the operation of each branch, remotely check the operation details of each branch, video monitoring, etc \n3. Remote mobile office: connect work and life, easy access to the company's OA system, file server, etc., can access the office computer at home \n4. Build personal private LAN: good gay friends love to beat boss, set up personal private LAN, realize multiplayer online games, enjoy the fun of games, gay love not cool \n\nUsage: \nOne APP only takes one step to realize intelligent networking: \nDifferent devices, such as two phones, download and install dandelion APP, and log on to the same oray account, which is to complete the automatic networking. \n\nDandelion, build a virtual LAN step by step, the end of the world. \nFor more, please follow: www.oray.com", "genres": [ "Utilities", "Business" ], "artistId": 551941462, "artistName": "Shanghai Best Oray Information Technology Co., Ltd.", "price": 0.00, "bundleId": "com.oray.pgy.PgyVisitorMac", "version": "3.4.32010", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple118/v4/f8/42/8d/f8428dc7-22fd-a938-97f9-f1538ce4042a/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/4a/d6/08/4ad60824-ae70-8fcb-261b-be798534e8ab/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/4a/d6/08/4ad60824-ae70-8fcb-261b-be798534e8ab/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/4a/d6/08/4ad60824-ae70-8fcb-261b-be798534e8ab/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/islandbit/id1073040697?mt=12&uo=4", "kind": "mac-software", "trackId": 1271281910, "trackName": "Activebot for Slack", "releaseDate": "2017-09-30T10:35:35Z", "genreIds": [ "6000" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.9", "primaryGenreName": "Business", "currentVersionReleaseDate": "2017-09-30T10:35:35Z", "primaryGenreId": 6000, "sellerName": "Islandbit Inc.", "currency": "USD", "trackCensoredName": "Activebot for Slack", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "4274788", "sellerUrl": "https://www.islandbit.com/activebot-for-slack/", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/activebot-for-slack/id1271281910?mt=12&uo=4", "trackContentRating": "4+", "description": "Activebot for Slack prevents Slack from going idle due to inactivity.\n\nNOTES:\nThis application requires Slack authentication using one of the following methods:\n1) \"Activebot for Slack\" app installed for your team\n2) Legacy token\n\nPlease verify you have access to adding apps to your Slack team or generating legacy tokens before purchasing this application at:\n\nhttps://api.slack.com/custom-integrations/legacy-tokens", "genres": [ "Business" ], "artistId": 1073040697, "artistName": "Islandbit", "price": 0.00, "bundleId": "com.islandbit.Activebot", "version": "1.0", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple49/v4/dd/08/a4/dd08a4fe-9df6-3265-c2d3-2c0cf68203e0/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple49/v4/8d/ad/b6/8dadb62c-32d2-621c-001d-0f46c57ad7ae/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple49/v4/a3/94/68/a3946880-3cf4-047e-71be-32f3ce8ea42f/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple49/v4/a3/94/68/a3946880-3cf4-047e-71be-32f3ce8ea42f/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple49/v4/a3/94/68/a3946880-3cf4-047e-71be-32f3ce8ea42f/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/gui-ning/id1086339124?mt=12&uo=4", "kind": "mac-software", "trackId": 1097510182, "trackName": "Building Block - Castle & City Craft Simulator", "releaseDate": "2016-04-01T22:03:42Z", "genreIds": [ "6014", "7017", "7015" ], "formattedPrice": "$2.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.7", "primaryGenreName": "Games", "currentVersionReleaseDate": "2016-04-12T00:18:16Z", "releaseNotes": "bugfixed", "primaryGenreId": 6014, "sellerName": "GUI NING", "currency": "USD", "trackCensoredName": "Building Block - Castle & City Craft Simulator", "languageCodesISO2A": [ "EN", "ZH" ], "fileSizeBytes": "2165740", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/building-block-castle-city-craft-simulator/id1097510182?mt=12&uo=4", "trackContentRating": "4+", "description": "BuildingBlock is a sandbox game with pixel styles & first-person.\n \n BuildingBlock has no specific goals for the player to accomplish, allowing players a large amount of freedom in choosing how to play the game. BuildingBlock enable players to build constructions out of textured cubes in a 3D procedurally generated world.\n \n Players have unlimited resources to build with and the ability to fly. At the start of the game, the player is placed on the surface of a procedurally generated and virtually infinite game world. The world is divided into biomes ranging from deserts to jungles to snowfields.Players can walk across the terrain consisting of plains, mountains, forests, caves, and various water bodies.\n\n The core gameplay revolves around breaking and placing blocks. The game world is composed of rough 3D objects—mainly cubes—arranged in a fixed grid pattern and representing different materials, such as dirt, stone, various ores, water, lava, tree trunks, etc. While players can move freely across the world, objects can only be placed at fixed locations on the grid. Players can gather these material blocks and place them elsewhere, thus allowing for various constructions.", "genres": [ "Games", "Strategy", "Simulation" ], "artistId": 1086339124, "artistName": "GUI NING", "price": 2.99, "bundleId": "com.gnmac.buildingblock", "version": "3.6", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple71/v4/6c/28/03/6c280331-3486-49e6-9668-5b15cc05ebcd/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple62/v4/d8/1c/3c/d81c3c7e-9bb0-8e86-f9f1-0395986e66f0/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple71/v4/a5/e1/30/a5e130a9-58e8-b993-63ab-ba43ed816a85/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple71/v4/cc/93/c9/cc93c904-3d54-c25d-5ec4-81900dcb3511/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple62/v4/4c/1e/a0/4c1ea0de-411a-2602-0ea1-040ea79ae172/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/00/1d/03/001d037b-392e-e2d6-126b-21e02f8dc263/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/00/1d/03/001d037b-392e-e2d6-126b-21e02f8dc263/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/00/1d/03/001d037b-392e-e2d6-126b-21e02f8dc263/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/shape-gmbh/id285688937?mt=12&uo=4", "kind": "mac-software", "trackId": 1137958278, "trackName": "IM+ All-in-One Messenger", "releaseDate": "2016-08-03T13:01:02Z", "genreIds": [ "6005", "6012" ], "formattedPrice": "$17.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Social Networking", "currentVersionReleaseDate": "2020-08-11T16:00:02Z", "releaseNotes": "- Various UI and counter fixes\n\nEnjoy and let us know your feedback,\nYour IM+ Team :)", "primaryGenreId": 6005, "sellerName": "SHAPE GmbH", "currency": "USD", "trackCensoredName": "IM+ All-in-One Messenger", "languageCodesISO2A": [ "AM", "AR", "BN", "BG", "CA", "HR", "CS", "DA", "NL", "EN", "ET", "FI", "FR", "DE", "EL", "GU", "HE", "HI", "HU", "ID", "IT", "JA", "KN", "KO", "LV", "LT", "MS", "ML", "MR", "NB", "FA", "PL", "PT", "RO", "RU", "SR", "ZH", "SK", "SL", "ES", "SW", "SV", "TA", "TE", "TH", "ZH", "TR", "UK", "VI" ], "fileSizeBytes": "67130312", "sellerUrl": "https://plus.im", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/im-all-in-one-messenger/id1137958278?mt=12&uo=4", "trackContentRating": "4+", "description": "IM+ supports Zoom, WhatsApp, Telegram, Facebook Messenger, Facebook, Twitter, Slack, Skype, Gmail, Outlook, Hangouts, LinkedIn Messaging, Instagram, SnapMap, WeChat, ChatWork, HipChat, and Facebook Pages in one simple app.\n\nLog in once into your favorite messengers and video communicators, and gone will be the days of endless app switching.\n\nSome of the best features:\nVideo and voice calls on Zoom and Skype from one app\nNavigate through your accounts using the sidebar or hotkeys \nAdd multiple accounts per service, e.g. add a couple Twitter profiles\nDistinguish messengers easier with color tags and filter them by profile tags\nSupport for file sharing\nManage notification settings for all accounts at once or per service \nGreat for remote workers\n\nThe whole IM+ experience was designed for Mac OS X. \nIf IM+ improves your messaging life, we would appreciate an App Store review. Thank you!\n\nIM+ for iPhone has been used by over 10 million people and featured on Wired, Engadget, TechCrunch, Forbes, Bloomberg, The New York Times, and many more. And you will love IM+ on Mac too!\n\nLearn more about IM+ at www.shape.ag\nWe listen to you, so send us your feedback, questions and feature requests on Twitter @implus or Facebook www.facebook.com/plusim", "genres": [ "Social Networking", "Lifestyle" ], "artistId": 285688937, "artistName": "SHAPE GmbH", "price": 17.99, "bundleId": "com.shapeservices.implusosx", "version": "1.13", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple111/v4/00/30/f5/0030f509-ed0f-d322-f24a-133922506d10/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple91/v4/bb/d2/2d/bbd22d87-6f95-b0be-beb6-fb4c0b5e1afc/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple111/v4/f3/42/a7/f342a728-11c0-e736-473a-a183dc3c097b/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple111/v4/09/39/c1/0939c16d-c9e4-6342-16cb-607481f8264f/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple122/v4/35/6f/30/356f3094-6062-7fc9-c237-ab59035fc366/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/ec/6b/6d/ec6b6d54-e990-3468-da0f-e5516c37620b/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/ec/6b/6d/ec6b6d54-e990-3468-da0f-e5516c37620b/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/ec/6b/6d/ec6b6d54-e990-3468-da0f-e5516c37620b/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/d2-nova-corp/id1120545372?mt=12&uo=4", "kind": "mac-software", "trackId": 1120731995, "trackName": "Team.biz - Business Messaging", "releaseDate": "2016-09-03T10:32:21Z", "genreIds": [ "6000", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Business", "currentVersionReleaseDate": "2019-11-14T00:51:05Z", "releaseNotes": "Function and UI enhancement. Security improvement. Support macOS10.15 Catalina.\nWe are continuing to make updates to Team.Biz in response to your feature requests.", "primaryGenreId": 6000, "sellerName": "D2 Nova Corp", "currency": "USD", "trackCensoredName": "Team.biz - Business Messaging", "languageCodesISO2A": [ "AM", "AR", "BN", "BG", "CA", "HR", "CS", "DA", "NL", "EN", "ET", "FI", "FR", "DE", "EL", "GU", "HE", "HI", "HU", "ID", "IT", "JA", "KN", "KO", "LV", "LT", "MS", "ML", "MR", "NB", "FA", "PL", "PT", "RO", "RU", "SR", "ZH", "SK", "SL", "ES", "SW", "SV", "TA", "TE", "TH", "ZH", "TR", "UK", "VI" ], "fileSizeBytes": "86876996", "sellerUrl": "https://www.team.biz", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/team-biz-business-messaging/id1120731995?mt=12&uo=4", "trackContentRating": "4+", "description": "This is the messaging platform for professionals. We've brought the power and speed of a modern messaging app and added the capabilities needed for it to make sense within a work environment.\n\nUse Team.biz for free. If you don't already have an account, you can sign up for one for free through the app.\n\n• Create your own private team conversation space\n• Reach anyone on your team with direct messages\n• Have private conversations with a subset of your team\n• Create open topics that are viewable by everyone on your team\n• Safely communicate outside your team by inviting guests to a specific topic or conversation\n• Text out (SMS and MMS) to guests [This feature is limited to US phone numbers]\n• Hold a conversation with team members, guests, and text message participants (SMS and MMS) at the same time \n• Delegate admin responsibility to other users, so that they can manage your account\n• Maintain your message history\n• Share documents and images\n• Quickly see who's online, who's in the conversation, and who’s typing\n• Messages can be edited after they've been sent, so you can correct small errors and mistakes that would otherwise appear unprofessional\n• View the change history for any message, so you always will know what was originally said", "genres": [ "Business", "Productivity" ], "artistId": 1120545372, "artistName": "D2 Nova Corp.", "price": 0.00, "bundleId": "com.d2nova.teamasapmac", "version": "2.0.3", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/33/f2/fd/33f2fdac-d025-d8e9-4945-5f97e9cc52d6/36ee117c-f4b0-4d22-b961-100f7f5b2ee2_1.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/21/1b/ef/211bef90-4006-c47f-ecf4-fe7045a636ea/062ce278-6bc1-4d63-be85-52293e7ee4ac_2.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/a2/75/b8/a275b835-d9aa-6c8a-f075-11f62788bcdf/89fca711-e8a5-4c1c-86e4-6dade08eead0_3.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/14/01/3b/14013b1f-6047-7072-c8dc-cee3b743f379/633d921a-3778-45b6-b2cd-7fec1c190fb6_4.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/fe/57/3f/fe573f18-6059-d6f0-c486-c448b82e20d4/78736982-88be-4cd2-a8f2-a73030a57d3a_5.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/f9/c1/ae/f9c1ae33-6590-8686-847c-004d6f15bdfb/b2698bb2-c1f9-4059-bf53-d67111704c4d_6.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/f3/06/c9/f306c9c4-9c32-e5c5-6ca2-2e625dde083e/e3be743f-2410-4e3e-af56-cf7b1b02bf40_7.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/d6/e5/04/d6e50418-e1ea-3b13-c060-4c46f7c27462/cf8b7740-bdbb-40d4-b65b-6b5142f11b6a_8.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/db/17/da/db17da9b-efbd-0e95-53ab-b506986dbec3/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/db/17/da/db17da9b-efbd-0e95-53ab-b506986dbec3/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/db/17/da/db17da9b-efbd-0e95-53ab-b506986dbec3/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/works-mobile-corp/id1012129121?mt=12&uo=4", "kind": "mac-software", "trackId": 1029784963, "trackName": "LINE WORKS", "releaseDate": "2016-07-07T08:07:12Z", "genreIds": [ "6000", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.12", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-09-02T01:19:27Z", "releaseNotes": "- Fixed other bugs and improved other features", "primaryGenreId": 6000, "sellerName": "WORKS MOBILE Corp.", "currency": "USD", "trackCensoredName": "LINE WORKS", "languageCodesISO2A": [ "EN", "JA", "KO", "ZH", "ZH" ], "fileSizeBytes": "21001800", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/line-works/id1029784963?mt=12&uo=4", "trackContentRating": "4+", "description": "LINE WORKS texting/free calling services via PC version are available. \nCommunicate with your colleagues more conveniently and quickly using the PC version.\n\n[Messaging function optimized for business]\nYou can easily communicate with members in a team/group chat room synced with your company’s organization chart, and share works and files in Notes. You can also communicate for free with LINE users and as well as other LINE WORKS users.\n※ Chatting with other company people is available after approval from the admin.\n\n[Transmission of multi files/photos and Neatly arranged screen]\nYou can easily send many photos or files at once by using Drag & Drop. Were you distracted by many message windows scattered on the screen? It collects many message windows shown on the screen into one and arranges them neatly.\n※ The maximum capacity and number of transmittable files : Maximum 100MB per each file, Maximum 20 files\n※ If you set the feature through Settings > Message / Call > ‘Merge windows’, you can see the collected and arranged message windows from that moment.\n\n[Free voice and video call through PC!]\nIsn’t it hard to explain by using messages? Now you can communicate more easily through free call service. By clicking the call icon from Colleague’s profile photo, you can use free voice and video call services. You can communicate with the Colleague while seeing each other’s faces despite the long distance between you and your Colleague.\n\n※ Available only for Mac OS X 10.12 and above.\n※ In the case Lite products are used, some parts of the features are restricted.", "genres": [ "Business", "Productivity" ], "artistId": 1012129121, "artistName": "WORKS MOBILE Corp.", "price": 0.00, "bundleId": "com.naver.WorksOneApp", "version": "2.9.2", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple128/v4/21/00/ae/2100ae81-9e04-5936-f6fa-510ba86560ea/mzl.kcqgyxnk.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/f7/34/e9/f734e9f6-09cb-b523-5f2c-1bde17972491/mzl.fpnstdmz.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple128/v4/8b/20/97/8b209744-90e0-de25-6b0a-816f0cf03a45/mzl.bhdbkhmo.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple128/v4/89/d7/36/89d736e4-e616-07fc-b676-243be06a2767/mzl.yoxlprei.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple128/v4/16/3e/6d/163e6d41-9e4d-02fa-62a2-d9bae16808cf/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple118/v4/07/f1/47/07f1477d-37a5-c3ff-abed-ed9e8e28a95a/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple118/v4/07/f1/47/07f1477d-37a5-c3ff-abed-ed9e8e28a95a/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple118/v4/07/f1/47/07f1477d-37a5-c3ff-abed-ed9e8e28a95a/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/appyogi-software/id1039633008?mt=12&uo=4", "kind": "mac-software", "trackId": 1139742811, "trackName": "One Chat All-in-One Messenger", "releaseDate": "2016-09-03T13:00:06Z", "genreIds": [ "6005", "6002" ], "formattedPrice": "$19.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.11", "primaryGenreName": "Social Networking", "currentVersionReleaseDate": "2018-03-01T05:10:31Z", "releaseNotes": "▸ Implement touch ID Password unlock for macs with touch bar. \n▸ Fixed Skype and WhatsApp freezing issue. \n▸ Fixed Slack notification issue. \n▸ Fixed all the bugs reported on support tickets.\n▸ Major bug fix.\n▸ QQ & WeChat does not work for new accounts as they does not allow to use.\n\nImprovements in Previous versions:\n\n▸ Downloading the audio file in WhatsApp is fixed.\n▸ New messaging service integrated -> Tinder chat\n▸ WhatsApp video recording handled.\n▸ HipChat automatic opening of Members List after new message arrival issue rectified.\n▸ Slack login using single sign-on (SSO) is handled.\n▸ Spelling check for all messaging services is enabled.\n▸ Skype drag and drop image option fixed.\n▸ Telegram image load issue with blue question mark handled\n▸ Facebook two factor authentication login handled\n▸ Added option to Enable/Disable sound notification alerts (in preferences)\n▸ No Internet connection' pop-up showing even for weak signals rectified.\n▸ Option to remember the password. \n▸ New services added are IRC chat, MySMS, Tweetdeck. \n▸ Option to stop GIF prompt while typing the message. \n▸ Option to mark all messages as reading.\n▸ Now you can send GIF instead of just a text message. \n▸ Schedule message to send them later.\n▸ FaceTime calling from WhatsApp\n▸ FaceTime calling from Telegram\n▸ Added Touch bar support for New MacBook Pro(2016)\n▸ Improvements in User Interface\n▸ Improved user preferences for more customisations\n▸ Fixed Skype calling plugin.\n▸▸ Multiple (Unlimited) Accounts for all messaging services.(hope you love it)\n▸ Individual Notifications for every accounts added\n▸ Rearrange messaging services on the menu.\n▸ Fixed shortcuts keys. \n▸ Added indicator for file download. \n▸ Fixed network connection status. \n▸ Take video and selfie from WhatsApp.\n▸ Notification Badges for individual chats services. \n▸ Added Twitter service\n▸ Loading issue resolved\n▸ Fixed Hangouts and Skype calling.\n▸ Fixed upload of media, docs and other files from the folder. \n▸ Added more services, Google Hangouts, HipChat, WeChat \n▸ Supports New MacOS Sierra Version: 10.12\n▸ Password Protection for privacy\n▸ Now use two different accounts of every service(messenger)\n▸ Add/Remove services from the left menu, as per the requirement.\n▸ Customize Notifications as per different accounts.\n▸ Fixed Media(Picture, Video or music) upload and download issue.\n▸ Fixed URL click issue on Facebook messenger.\n\n▸ If you enjoy using \"One Chat\" App, Please review and rate. That keeps us motivated. \n\nNote: Some new and existing features might not work in below MacOS Sierra.\nBut lower OS version users can use it with limited functionality. \n\nCurrent messaging services 22 and more coming:\nWhatsApp, FB Messenger, Hangouts, Telegram, Skype, Slack, WeChat, HipChat, Twitter, Yahoo Chat, ICQ, Discord, GroupMe, QQ, Messenger for Facebook Pages, Fleep, VK, RocketChat, Tinder.\n\nUnder the hood improvements: \n▸ Avg. CPU reduced from 10% to 3% \n▸ Avg. Energy consumption reduced from High to Zero\n\nFeatures in next version: \n▸ More messaging services. (Instagram, Line, Kik, Linkedin, wire, Tinder, Tumbler, XXMP, Steam, Grape)\n▸ And many more surprises.\n\nSpecial Thanks to our all existing user with following users who are helping us with suggestions and support.\nCosmin Miron\nRon Brathwaite\nDirk Gates\nLuis Neves\nRahulmhatre\nwa.k\nAlexander Leijenaar. \n\nWe respect all the suggestions and requests from our users and implement, which we feel are most important and solves the real problems of all users. \n\n\nOverall, using One Chat does provide a couple of advantages when it comes to security.\nIt does not store any messages or logins.", "primaryGenreId": 6005, "sellerName": "AppYogi Software", "currency": "USD", "trackCensoredName": "One Chat All-in-One Messenger", "languageCodesISO2A": [ "DA", "NL", "EN", "FI", "FR", "DE", "EL", "ID", "IT", "JA", "KO", "MS", "NB", "PT", "RU", "ZH", "ES", "SV", "TH", "ZH", "TR", "VI" ], "fileSizeBytes": "8149607", "sellerUrl": "https://appyogi.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/one-chat-all-in-one-messenger/id1139742811?mt=12&uo=4", "trackContentRating": "4+", "description": "One App for all messaging services.\n\nWhatsApp, Facebook Messenger, Hangouts, Telegram, Skype, Slack, WeChat, Twitter, YahooChat, ICQ chat, HipChat, Discord, GroupMe, QQ, Facebook Page messenger VK, Fleep, RocketChat, MySMS, IRC Chat, TweetDeck Tinder.\n\nNote: One Chat is a wrapper app, combining all the messaging services. Experience will be 100% similar to web extension of those services, which extra features as follows. \n\n▸ QQ & WeChat only works for old users of them. \n\nFeatures:\n▸ Schedule message to send them at a planned time. \n▸ Send and receive Photos, Videos, doc, PDF..etc\n▸ Use Multiple parallel accounts of the same service.\n▸ Touch Bar support in 10.12 to select messaging services. \n▸ Remember passwords for easy login. \n▸ Get respective Notifications alerts and reply from the same.\n▸ Auto Smart Gifs for messaging friends\n▸ Mark all unread messages read with just one Click. \n▸ Privacy mode to protect your messaging data with touch ID.\n▸ Customize messaging service order in the side menu.\n▸ FaceTime calling from WhatsApp & Telegram numbers.\n▸ Customize notification as per your requirement.\n▸ Shows notifications badges for individual services.\n▸ Check if there are any unread messages, right in the menu bar.\n▸ Go Full-screen! Chat with your friends, distraction-free in the full-screen mode.\n▸ Lightweight all, with minimum installation size.\n▸ Retina Display enabled icons\n▸ Easy to use user-interface(UI) and user-experience(UX)\n\nUpcoming update:\n- More chat services, suggestions most welcome.\n- And much more.\n\n>>>>>>>> Download Now <<<<<<<<\n\nDisclaimer:\nOne Chat is a third party App. The developer of this software is not affiliated with WhatsApp Inc. Facebook Inc. Telegram Inc. Slack Inc. Skype Inc. Google Inc. Atlassian Inc. Tencent Inc. Twitter Inc. V Kontakte. Yahoo Inc. Sleep Inc. Fleep Inc, RocketChat Inc., MySMS Inc, Tinder Inc., in any way.", "genres": [ "Social Networking", "Utilities" ], "artistId": 1039633008, "artistName": "AppYogi Software", "price": 19.99, "bundleId": "com.appyogi.onechat", "version": "4.2", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/a3/09/1e/a3091e8d-c9fc-efdf-434c-3769715d2a3d/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/8d/8d/6f/8d8d6fcc-5e7d-5978-9be7-2ce6a23b2810/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/12/7a/7f/127a7f31-a9f9-b222-0d1b-c064065f5b65/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/72/84/81/7284818a-5bd3-0429-8658-b0038efbf67d/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/ef/bc/d7/efbcd7a7-3f1e-ba1f-2d55-bbba9b8c55f9/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/62/30/60/6230603f-7698-1cd7-9601-732a3ae74f8d/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/71/3e/2b/713e2ba7-0169-a4f2-b1c5-09252f5fcedd/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/71/3e/2b/713e2ba7-0169-a4f2-b1c5-09252f5fcedd/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/71/3e/2b/713e2ba7-0169-a4f2-b1c5-09252f5fcedd/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/swit-technologies-inc/id1451271780?mt=12&uo=4", "kind": "mac-software", "trackId": 1464411410, "trackName": "Swit - Team collaboration hub", "releaseDate": "2019-06-04T01:46:30Z", "genreIds": [ "6000" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-09-01T04:58:09Z", "releaseNotes": "Updates\n* UI/UX and performance improvements\n \nBug Fixes\n* Other minor bug fixes", "primaryGenreId": 6000, "sellerName": "SWIT TECHNOLOGIES INC.", "currency": "USD", "trackCensoredName": "Swit - Team collaboration hub", "languageCodesISO2A": [ "AM", "AR", "BN", "BG", "CA", "HR", "CS", "DA", "NL", "EN", "ET", "FI", "FR", "DE", "EL", "GU", "HE", "HI", "HU", "ID", "IT", "JA", "KN", "KO", "LV", "LT", "MS", "ML", "MR", "NB", "FA", "PL", "PT", "RO", "RU", "SR", "ZH", "SK", "SL", "ES", "SW", "SV", "TA", "TE", "TH", "ZH", "TR", "UK", "VI" ], "fileSizeBytes": "106927881", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/swit-team-collaboration-hub/id1464411410?mt=12&uo=4", "trackContentRating": "4+", "description": "Swit is one work suite for Chat and Project Management. With Swit, you can replace Slack and Trello (or Asana).\n\nSwit provides seamless workflows coupling chat and tasks in one convenient place. We have all the features that Slack and Trello (or Asana) have - faster, better, and safer.\n\n * Team chat\n * Ideation post\n * To-do kanban\n * Gantt chart\n * Calendar\n * Advanced integrated search\n\nTechCrunch - “a collaboration suite that offers freedom from integrations”\nBusiness Insider - “flexible enough to accommodate the needs of multiple departments within enterprises”\nInfoTech - “one central hub to delineate a wide variety of processes”\nSaaStock - \"Top 4 at the West Coast, Top 10 Rising Stars\"\nFortune - “Swit, a San Francisco-based workplace team collaboration app, raised $6 million in funding.”\nGSVlabs - “Now that everything is in one place it's easy to find conversations had in messages and relate them directly to project tasks.”\nWeb Summit - “The Future of Team Collaboration Apps”\n\nSign up for FREE forever\n\nWork Sweet on Desktop and Mobile!", "genres": [ "Business" ], "artistId": 1451271780, "artistName": "SWIT TECHNOLOGIES INC.", "price": 0.00, "bundleId": "com.swit.desktop", "version": "2.9.9", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/a5/e7/65/a5e765b9-6518-c932-fbed-db6d80828adf/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/57/03/ec/5703ecf0-c694-9ccd-52c2-21d37b04e651/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/57/03/ec/5703ecf0-c694-9ccd-52c2-21d37b04e651/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/57/03/ec/5703ecf0-c694-9ccd-52c2-21d37b04e651/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/sai-praneeth/id1071875110?mt=12&uo=4", "kind": "mac-software", "trackId": 1510447358, "trackName": "Slacker - auto mouse mover", "releaseDate": "2020-06-10T07:00:00Z", "genreIds": [ "6002", "6000" ], "formattedPrice": "$2.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.15", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2020-06-11T04:47:14Z", "primaryGenreId": 6002, "sellerName": "Sai Praneeth", "currency": "USD", "trackCensoredName": "Slacker - auto mouse mover", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "635428", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/slacker-auto-mouse-mover/id1510447358?mt=12&uo=4", "trackContentRating": "4+", "description": "Do you work smart, not hard? Well that's great, but your boss and team mates might be forming opinions based on whether you show up as active or available or online on your office's instant messaging tools like Slack, Skype, Microsoft Teams and more. \n\nOnce you stay idle for some time, these messaging tools display your status as away or idle or offline causing your co-workers to suspect you are not in front of your computer all the time. You know how big a problem this is if you WFH (work from home) regularly and can't constantly be in front of your computer.\n\nSlacker solves this by moving your mouse pointer for you whenever you are idle - the result is that you always show as active on any corporate/office instant messaging tools (like Slack or Microsoft Teams) running on your work computer.\n\nThis additionally also means that your screen lock and screensaver will not be activated and your system will not go to sleep just because you step away from your computer while working from home or in the office.", "genres": [ "Utilities", "Business" ], "artistId": 1071875110, "artistName": "Sai Praneeth", "price": 2.99, "bundleId": "com.inchwest.Slacker", "version": "1.3", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple118/v4/dd/49/f7/dd49f7b7-7cdd-183e-977f-369d2b638f5a/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple118/v4/9b/cf/e8/9bcfe870-f18b-73fd-4169-e4b32b7dba4f/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/18/6f/40/186f4019-5989-3f71-6d9f-a0b01b906e1c/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple118/v4/e9/37/3e/e9373e04-c404-6ff4-05e2-a4f397aa090a/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/7c/fc/83/7cfc83aa-5d6a-b66f-233f-4e6e0da8cc79/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/7c/fc/83/7cfc83aa-5d6a-b66f-233f-4e6e0da8cc79/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/7c/fc/83/7cfc83aa-5d6a-b66f-233f-4e6e0da8cc79/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/qin-hu/id822142008?mt=12&uo=4", "kind": "mac-software", "trackId": 1229446984, "trackName": "MyTasks for Asana", "releaseDate": "2017-05-19T20:32:20Z", "genreIds": [ "6007", "6000" ], "formattedPrice": "$4.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2018-01-07T20:48:30Z", "releaseNotes": "Minor bug fixes.", "primaryGenreId": 6007, "sellerName": "qin hu", "currency": "USD", "trackCensoredName": "MyTasks for Asana", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "330338", "sellerUrl": "https://enjoyableapps.wordpress.com/mytasks-for-asana", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/mytasks-for-asana/id1229446984?mt=12&uo=4", "trackContentRating": "4+", "description": "A native client for Asana on macOS.\n\nFEATURES\n• Create tasks easily.\n• View tasks from the menu-bar.\n--- List of tasks assigned to you.\n--- List of tasks you follow.\n--- List of tasks in a project.\n• Mark tasks as completed.\n• Like and unlike a task.\n• Comment on tasks.\n• Get notifications when there are new tasks and updated tasks.\n• Sort your tasks by due time, created time, updated time, or number of likes.\n• Filter your tasks by projects, tags, or status.\n\nNOTE\nMyTasks for Asana is a 3rd party application for Asana and is in no way endorsed or affiliated with Asana, Inc.", "genres": [ "Productivity", "Business" ], "artistId": 822142008, "artistName": "qin hu", "price": 4.99, "bundleId": "com.huqin.MyTasks-for-Asana", "version": "1.6", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/f4/65/99/f4659920-43c5-3cc1-036b-e569e5fddbcc/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/58/37/f4/5837f430-7748-62ea-f0b7-c4dce867ed8a/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/a4/35/f1/a435f1fc-7ad0-8b09-c082-b514bdcb9033/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/55/dd/4b/55dd4be3-dce8-11c4-8916-97040fddd3b7/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/90/f3/75/90f37528-b79d-0279-5362-4f0e47a354c9/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/90/f3/75/90f37528-b79d-0279-5362-4f0e47a354c9/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/90/f3/75/90f37528-b79d-0279-5362-4f0e47a354c9/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/ubergrape-gmbh/id943855716?mt=12&uo=4", "kind": "mac-software", "trackId": 971791845, "trackName": "Grape - Messenger", "releaseDate": "2015-04-08T02:17:47Z", "genreIds": [ "6000", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-07-17T17:13:52Z", "releaseNotes": "- Added support for Polish language.", "primaryGenreId": 6000, "sellerName": "UberGrape GmbH", "currency": "USD", "trackCensoredName": "Grape - Messenger", "languageCodesISO2A": [ "AM", "AR", "BN", "BG", "CA", "HR", "CS", "DA", "NL", "EN", "ET", "FI", "FR", "DE", "EL", "GU", "HE", "HI", "HU", "ID", "IT", "JA", "KN", "KO", "LV", "LT", "MS", "ML", "MR", "NB", "FA", "PL", "PT", "RO", "RU", "SR", "ZH", "SK", "SL", "ES", "SW", "SV", "TA", "TE", "TH", "ZH", "TR", "UK", "VI" ], "fileSizeBytes": "83490302", "sellerUrl": "https://www.grape.io/press/", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/grape-messenger/id971791845?mt=12&uo=4", "trackContentRating": "4+", "description": "A different messenger.\nSecure video, voice and text conversations that happen inside your favourite business tools.\n\n- Real time messaging, file sharing, supporting private messages and group conversations.\n- Run it inside your favourite tools like Jira, Confluence or Sharepoint.\n- Reimagined Enterprise Search helps you quickly find anything within your business, including services, documents, appointments, files, contacts and more. \n- Instantly synced across all devices.\n- Configurable notifications for desktop, mobile push and email.\n- Host it on-premises or in the secure Euro-Cloud", "genres": [ "Business", "Productivity" ], "artistId": 943855716, "artistName": "UberGrape GmbH", "price": 0.00, "bundleId": "com.ChatGrape", "version": "3.1.0", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/ce/e2/9d/cee29db7-0202-ee16-2d5e-e3beb87323cc/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/49/81/95/4981952e-c172-68ea-38ae-3c082539959e/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/ff/3a/47/ff3a47e3-66a0-91fd-5777-9b58922c1af1/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/41/e8/d6/41e8d6e4-7ba0-ef42-08ec-bd0f6ecd20f7/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/2d/b5/b9/2db5b92b-3aeb-0bf2-5dc3-592c98be60b4/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/ed/b0/c4/edb0c40a-5bc3-c67d-1ef2-9a4aa5a0955d/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/3e/58/7c/3e587cf0-0400-7339-2add-96a7abd91e24/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/5e/9e/c5/5e9ec51a-b500-2da3-5d50-94c2e616b35e/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/09/80/7f/09807fde-00e8-d19f-1a06-d20bbc9f967f/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/09/80/7f/09807fde-00e8-d19f-1a06-d20bbc9f967f/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/09/80/7f/09807fde-00e8-d19f-1a06-d20bbc9f967f/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/octopus-think/id1489816365?mt=12&uo=4", "kind": "mac-software", "trackId": 1489816366, "trackName": "Mic Drop.", "releaseDate": "2020-04-14T07:00:00Z", "genreIds": [ "6002", "6007" ], "formattedPrice": "$6.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.15", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2020-08-03T19:32:54Z", "releaseNotes": "We fixed a bug that could cause Mic Drop to crash/hang if you were using Loopback audio devices.", "primaryGenreId": 6002, "sellerName": "Octopus Think Ltd.", "currency": "USD", "trackCensoredName": "Mic Drop.", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "1746857", "sellerUrl": "https://getmicdrop.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/mic-drop/id1489816366?mt=12&uo=4", "trackContentRating": "4+", "description": "Ever been in a video call and needed to turn off your microphone, but couldn’t find the mute button quickly enough?\n\nMic Drop helps you mute (and then unmute!) your microphone with a keyboard shortcut or a menu bar control. It works with every meeting app out there—from Zoom to Slack, FaceTime to Houseparty.\n\nIt also shows your mic status in a menu bar icon, so you'll always know at a glance whether you're muted or not. And it lets you (un)mute yourself even if you don't have your meeting app open.\n\nMic Drop:\n\n- Allows you to define a custom keyboard shortcut that works across your Mac.\n- Works with every app and audio device out there.\n- Automatically keeps you muted (or unmuted!) if you change audio devices.\n- Supports push-to-talk and push-to-mute.\n- Shows your microphone status in the menu bar.\n- Shows your microphone status in an optional floating window.\n- Respects your privacy. Mic Drop never listens to you.\n\nBecause Mic Drop mutes your audio at the system level, it works with any app you can imagine. We’ve tested with:\n\n- Zoom\n- Slack\n- Google Meet\n- Skype\n- Microsoft Teams\n- Discord\n- FaceTime\n- Vidyo\n- Houseparty\n- Cisco Webex\n- and many more.\n\nIt just works. No matter where you’re meeting, Mic Drop’s got you covered.\n\n----------\n\nA note on hardware compatibility:\n\nMic Drop works with just about any audio device out there, but some audio devices can't be muted by macOS, so Mic Drop can't control them. All bluetooth and wired headphones work—it's just some niche USB audio interfaces that Mic Drop won't mute. Mic Drop will warn you if it detects an incompatible device. A list of problematic devices are listed on our support page.", "genres": [ "Utilities", "Productivity" ], "artistId": 1489816365, "artistName": "Octopus Think", "price": 6.99, "bundleId": "com.octopusthink.Mic-Drop", "version": "1.1.6", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/85/61/24/85612409-4ed4-cffc-6df0-1f9cb8098019/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/13/53/65/13536517-9cc2-fe7a-5bcb-1dec9d887a8a/pr_source.jpg/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/d2/17/69/d217692c-c134-debd-d174-12ac27bd85e4/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/bf/38/40/bf3840b3-b4ab-b9ea-f9a3-ede48ad6c69a/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/ae/8c/b5/ae8cb50b-e5ce-2f5c-5347-9b94759a704c/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/b0/2f/ce/b02fce9a-f5bc-e9a6-b4e7-2d25a2876ca3/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/72/67/63/72676353-b09c-b71d-5a9d-f80765f9a5bd/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/a8/b2/54/a8b254b7-9a2d-cef2-5d73-681b6edbd774/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/a8/b2/54/a8b254b7-9a2d-cef2-5d73-681b6edbd774/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/a8/b2/54/a8b254b7-9a2d-cef2-5d73-681b6edbd774/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/sdwr/id560094566?mt=12&uo=4", "kind": "mac-software", "trackId": 940239558, "trackName": "Lists for Trello", "releaseDate": "2014-11-21T03:38:30Z", "genreIds": [ "6007", "6002" ], "formattedPrice": "$4.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.14", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2020-05-04T17:56:36Z", "releaseNotes": "- Performance and stability improvements", "primaryGenreId": 6007, "sellerName": "Alexey Linkov", "currency": "USD", "trackCensoredName": "Lists for Trello", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "3385009", "sellerUrl": "https://lists4trello.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/lists-for-trello/id940239558?mt=12&uo=4", "trackContentRating": "4+", "description": "Lists for Trello keeps you focused on current tasks. Toggle side panels and on-card indicators when you want more context and complexity.\n\n\n* Edit, archive, move and add cards across your lists, start dragging a card to see options.\n\n* Edit, delete or rename lists and boards by right clicking on one of the list items on the left\n\n* You can switch between all cards and only those cards assigned to you across all boards. Use the Crown Switch located on the left pane. \n\n* Cards can be sorted by due date. You can turn off sorting, sort ascending or descending clicking on the stopwatch icon on the left pane.\n\n* Cards can be marked with a special dot. You can choose from several criteria that should be met for the dot to be displayed. By default dot is shown for cards that have open todos, you can change that by opening View -> Show Dot When... in the top app menu\n\n* Due and overdue cards are color coded in list view. Overdue colored red and due are colored amber.\n\n* Color labels for cards are displayed in center pane. Right click on a card to add / remove labels. You can hide labels by going in Menu -> View -> Toggle Labels\n\n* Checklists and todo items for a card are accessible from right pane by flipping the switch on top. You can add, delete, rename and reorder todo items. You can click just once on a todo item to start editing it. Reordering of checklists as well as making a new card from a todo item will be available shortly.\n\n* Today widget\nLeft click on a list in the main application and select \"Show in Today Widget.\" to add it.\n\nUseful shortcuts:\n- CMD+R to reload cards in currently selected list\n- SHIFT+CMD+R to reload all lists and boards\n- CMD+1 to toggle sidebar\n- CMD+0 to toggle card inspector\n- CMD+L to toggle color labels on cards\n\nGive us feedback and see what features / fixes / improvements are coming in the next update by following us on https://www.facebook.com/lists4trello/", "genres": [ "Productivity", "Utilities" ], "artistId": 560094566, "artistName": "SDWR", "price": 4.99, "bundleId": "SDWR.Lists", "version": "2.7", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple5/v4/d2/c3/92/d2c392c5-e08e-7df8-c433-a3b296a6650a/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple5/v4/02/78/4f/02784fb5-7afa-228b-7726-ddf2aaaa0b84/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple5/v4/46/10/45/4610450f-2868-bf20-17c6-b684c49c0c89/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple7/v4/eb/b4/da/ebb4dacc-5062-77f4-5c99-ac9ebdf6ff99/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple7/v4/46/2c/0f/462c0f28-f865-25ad-3164-df261a5c2760/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple30/v4/95/83/04/9583045a-665d-a0be-dfc7-89487890d35d/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple30/v4/95/83/04/9583045a-665d-a0be-dfc7-89487890d35d/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple30/v4/95/83/04/9583045a-665d-a0be-dfc7-89487890d35d/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/eric-delabar/id823959467?mt=12&uo=4", "kind": "mac-software", "trackId": 994537604, "trackName": "ChannelScrobbler - Real-time scrobble bot for Last.fm/Slack", "releaseDate": "2015-06-01T17:54:13Z", "genreIds": [ "6005", "6011" ], "formattedPrice": "$2.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10", "primaryGenreName": "Social Networking", "currentVersionReleaseDate": "2016-07-06T01:11:45Z", "releaseNotes": "* Fixed bug where custom bot name was not saved between application launches.\n* Fixed artist info lookup/sterilization so that if Last.fm lookup fails original scrobble info is displayed instead.\n* Updated some metadata.", "primaryGenreId": 6005, "sellerName": "Eric DeLabar", "currency": "USD", "trackCensoredName": "ChannelScrobbler - Real-time scrobble bot for Last.fm/Slack", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "1202787", "sellerUrl": "https://ericdelabar.com/projects/channelscrobbler.html", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/channelscrobbler-real-time-scrobble-bot-for-last-fm-slack/id994537604?mt=12&uo=4", "trackContentRating": "4+", "description": "ChannelScrobbler is a fun way to post your Last.fm scrobbles to a Slack channel in real-time. Create a music channel and show your friends and co-workers what you're listening to and see their favorites as well. \n\nOther solutions like IFTTT poll the Last.fm feed and only update every 15 minutes or so, ChannelScrobbler allows you to choose your update period and see updates as frequently as every 30 seconds. \n\nChannelScrobbler runs in your status bar and can launch on startup, so it's always there, listing every track you play. Because ChannelScrobbler reads your Last.fm feed scrobbles from your account it will catch new songs from any device that supports scrobbling, not just your computer!\n\n* Includes retina graphics and support for dark status bars!\n* Scrobbles are posted by a bot with customizable name, avatar, and colors.\n\nIf you're having problems using ChannelScrobbler, please email edelabar@gmail.com for support.", "genres": [ "Social Networking", "Music" ], "artistId": 823959467, "artistName": "Eric DeLabar", "price": 2.99, "bundleId": "com.ericdelabar.ChannelScrobbler", "version": "1.2", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/bf/08/c7/bf08c74c-2313-d6a8-183e-1d7c23cfada4/5f0c1b7d-ce2f-42dd-a389-5acb3df5d9e5_screen-1.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/d6/e6/d3/d6e6d358-5a28-fe8a-5855-0c3e92fd92d1/6c770e65-6a0a-4ce1-a9a3-a88863128f9d_screen-2.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/0e/5c/0b/0e5c0b2e-24d0-fa91-2abc-a2141e059de9/af39f6ac-209e-434d-bc40-fe9262be4198_screen-3.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/2b/de/5e/2bde5e2e-cb46-4fe6-2ff4-e5487d2d4d35/0d7ddd32-f5ff-4253-8cd8-14170809d8d1_screen-4.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/84/e6/cd/84e6cd75-70ce-7146-7e2b-eca3c014bfb4/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/84/e6/cd/84e6cd75-70ce-7146-7e2b-eca3c014bfb4/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/84/e6/cd/84e6cd75-70ce-7146-7e2b-eca3c014bfb4/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/ilya-shaisultanov/id1155364065?mt=12&uo=4", "kind": "mac-software", "trackId": 1524355453, "trackName": "Thinking Face Emoji Tools", "releaseDate": "2020-07-24T07:00:00Z", "genreIds": [ "6005", "6002" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.12", "primaryGenreName": "Social Networking", "currentVersionReleaseDate": "2020-08-12T21:08:41Z", "releaseNotes": "Save options across reloads.", "primaryGenreId": 6005, "sellerName": "Ilya Shaisultanov", "currency": "USD", "trackCensoredName": "Thinking Face Emoji Tools", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "3757555", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/thinking-face-emoji-tools/id1524355453?mt=12&uo=4", "trackContentRating": "4+", "description": "Tools that make life as a Slack emoji addict a little easier.\n\nFeatures:\n• Drag & drop emoji upload\n• Bulk emoji deletion\n• Optional prefix and suffix for emoji names\n• Automatically overwrite existing emoji \n• Respect Slack API rate limiting\n\nEver wanted to upload 100 emoji to slack in one fell swoop? Now you can!\n\n1. Install this extension\n2. Go to your Slack's /customize/emoji page\n3. Drag and drop your emoji image files into the new \"Bulk Emoji Uploader\". File names become the emoji name, e.g. meow-party.gif becomes :meow-party: Customize them with an optional prefix or suffix!\n4. You've now got a whole lotta emoji!", "genres": [ "Social Networking", "Utilities" ], "artistId": 1155364065, "artistName": "Ilya Shaisultanov", "price": 0.00, "bundleId": "com.diversario.Thinking-Face-Emoji-Tools", "version": "1.3.1", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/a5/aa/c5/a5aac5fd-8acc-bc30-47aa-3243d9a37a15/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/89/a0/2c/89a02cfc-d14b-b789-b015-a1611bbd322f/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/61/70/03/6170030f-69b1-33f6-f59e-890d8bbbf648/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/40/1a/c0/401ac066-772a-01df-e339-2aee5e9cc919/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/41/40/5d/41405d96-802f-7f56-54ca-3bea7ce9c715/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/ae/a9/eb/aea9eb7c-b4ab-97e0-3f5d-8881bc65ad67/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/ae/a9/eb/aea9eb7c-b4ab-97e0-3f5d-8881bc65ad67/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/ae/a9/eb/aea9eb7c-b4ab-97e0-3f5d-8881bc65ad67/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/uptech-works-llc/id1190352695?mt=12&uo=4", "kind": "mac-software", "trackId": 1487662886, "trackName": "TeamSmash", "releaseDate": "2020-01-07T08:00:00Z", "genreIds": [ "6007", "6005" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.14", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2020-04-20T17:00:26Z", "releaseNotes": "Your team now has more control over when snapshots are taken!\n\n• Opt into manual capture mode to receive a notification as often as you want to remind you to snap a fun photo for your team! Your picture will not be taken automatically. Snap only when you want to!\n• For those of you with automatic capture turned on, you can also receive notifications right before your photo is taken. A perfect reminder to put on that fancy smile, a ridiculous face, or stop the photo altogether. Your choice!\n\nDon't forget, you can always change your capture mode and other settings by going to TeamSmash preferences menu! Oh, and we made some bug fixes and performance enhancements too.", "primaryGenreId": 6007, "sellerName": "UpTech Works, LLC", "currency": "USD", "trackCensoredName": "TeamSmash", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "5512772", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/teamsmash/id1487662886?mt=12&uo=4", "trackContentRating": "4+", "description": "TeamSmash is a companion app for Slack that lets you use video snapshots to enhance your remote working experience. \n\nQuickly see when your teammates are around and available to ask questions - or when they are away from their desk. \n\nYou can either take a picture of yourself manually or have the app take one automatically every so often. \n\nAnd if you need to jump into a video call, it's easy to start a Slack call with one of your co-workers. You are in complete control! \n\nBest of all, you can also use the built-in feed to memorialize any of your teammates with a funny meme of their picture. \n\nWorking remote should not feel lonely. Use TeamSmash and feel connected to your team!", "genres": [ "Productivity", "Social Networking" ], "artistId": 1190352695, "artistName": "UpTech Works, LLC", "price": 0.00, "bundleId": "ch.upte.TeamSmash.mac", "version": "1.2.0", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/8c/b8/6e/8cb86e19-7cea-205b-af78-b61242766400/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/64/f9/2f/64f92f39-6690-a3e1-c7ff-50978b750431/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/d4/e4/bb/d4e4bbf4-44ef-f844-5efa-f9a720f57c6e/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/cc/22/1c/cc221c0c-6e03-222e-e702-206afba31408/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/31/c6/24/31c6242f-5899-c99d-8062-89ce18beb2b0/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/e1/a2/fc/e1a2fc62-61aa-04cb-82aa-14ac07909a1e/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/c7/15/b6/c715b687-7bd2-0fe5-3f61-43cad60b4b04/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/c7/15/b6/c715b687-7bd2-0fe5-3f61-43cad60b4b04/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/c7/15/b6/c715b687-7bd2-0fe5-3f61-43cad60b4b04/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/simply-good-software/id385251756?mt=12&uo=4", "kind": "mac-software", "trackId": 1499496939, "trackName": "Pyrus", "releaseDate": "2020-06-11T07:00:00Z", "genreIds": [ "6000" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10.0", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-08-20T13:59:50Z", "releaseNotes": "Minor bug fixes.", "primaryGenreId": 6000, "sellerName": "Simply Good Software, Inc", "currency": "USD", "trackCensoredName": "Pyrus", "languageCodesISO2A": [ "EN", "RU" ], "fileSizeBytes": "80176195", "sellerUrl": "https://pyrus.com/en", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/pyrus/id1499496939?mt=12&uo=4", "trackContentRating": "4+", "description": "Pyrus is the team communication tool for your entire business. It incorporates real-time messaging, task delegation, and approval flows. Finally, all your tasks and conversations are in one easy-to-use interface.", "genres": [ "Business" ], "artistId": 385251756, "artistName": "Simply Good Software", "price": 0.00, "bundleId": "net.pyrus.macosclient", "version": "1.0.12", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/b8/57/43/b85743bc-16b6-4971-02dd-5b7c2162b5e3/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/81/70/f5/8170f593-ad4e-f377-17b3-ab0c30c279a2/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/72/ae/78/72ae7824-f4a0-4f3b-27e3-656c358e877a/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/f9/a9/26/f9a92636-e348-78c2-e00f-c04d8e319637/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/dd/c0/f7/ddc0f790-b7a5-cebd-30f2-4f55a1d73068/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/6d/5d/d5/6d5dd50e-7a42-8f3b-0c99-f3defe3d153f/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/b2/a8/9e/b2a89eaf-2c84-43b5-013b-29450ee5686d/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/50/03/83/50038321-48f9-3087-b698-733047b17969/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/dd/25/5b/dd255b82-3e85-0a1f-768b-0e0787f846f7/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/67/f1/7d/67f17dbf-9832-01c0-98d7-03e91320de8d/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/67/f1/7d/67f17dbf-9832-01c0-98d7-03e91320de8d/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/67/f1/7d/67f17dbf-9832-01c0-98d7-03e91320de8d/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/intrinsic-properties-llc/id1119328387?mt=12&uo=4", "kind": "mac-software", "trackId": 1438864470, "trackName": "Monitro", "releaseDate": "2019-10-21T07:00:00Z", "genreIds": [ "6002" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.14", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2019-12-08T01:45:12Z", "releaseNotes": "Minor bug fixes", "primaryGenreId": 6002, "sellerName": "Intrinsic Properties, LLC", "currency": "USD", "trackCensoredName": "Monitro", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "14008794", "contentAdvisoryRating": "17+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/monitro/id1438864470?mt=12&uo=4", "trackContentRating": "17+", "description": "Monitro is a macOS menu bar app that monitors folders for changes and can also provide simple IMAP mail processing as well.\n\nFor folders you create a list of folders you wish to have monitored. You can select details such as search depth and whether or not aliases should be followed. You can then apply a wide variety of timers to do the comparisons for changes ranging from every minute to something like weekly on Thursday at 2:30 PM. When changes are detected you can send a change report to combination of email addresses, a Slack web hook, or log file. Change detections are based on comparing to a database of file names, created dates, and modified dates. Using a database is important since you can actually quit the app and launch it any time in the future and it will report on any changes since it last saw the folders being monitored at the next reporting times.\n\nFor IMAP email processing you can apply simple rules to any email accounts you want to be monitored. You can query on To, From, CC, BCC, Subject, Body, Has Attachment, Attachment Name, Attachment Type, and for PDF or text files look at text within the attachment itself. When an email is matched you can apply actions on it such as Move, Copy, Delete, Mark Read, Mark Flagged, Mark Answered, or Save Attachments. It is important to note that you can actually Move or Copy messages between completely different IMAP accounts you have defined for access. It is usually very difficult to find these features server side and though some are possible client side with Monitro you do not need to have an email client running 24/7 to provide this level of filtering.\n\nMonitro is FREE for one Folder Monitor and one IMAP Monitor Rule. If you need more than that you can purchase a yearly subscription for $12.99 to unlock Full Access mode which let you run an unlimited number of monitors for folders or rules for IMAP accounts. If $12.99 seems high remember this is a very specific use app that is intended to unlock more time if your life if you find its features useful. It is not expected to attract a large paid user base. However, if the user base proves large enough the targeted use of funds is further feature development based on user feedback.\n\nMonitro is made for Mojave will not work on earlier versions of macOS. Unless you are saving attachments from IMAP accounts the only writing to your file system Monitro will do is to its own private settings and databases. It will not touch or otherwise store any information in the folders you monitor thus it is possible to have more than one monitor for a single folder and still have things work flawlessly. Monitro is made to play well as a sandboxed app but if you are looking to monitor folders applications normally do not have access to you will need to enable Full Disk Access on your own.\n\nHINT: For IMAP Rules saving attachments Monitro pairs perfectly with Haze from Noodlesoft. Hazel is an app from an unrelated developer that watches whatever folders you tell it to, automatically organizing your files according to the rules you create.", "genres": [ "Utilities" ], "artistId": 1119328387, "artistName": "Intrinsic Properties, LLC", "price": 0.00, "bundleId": "com.intrinsicproperties.Monitro", "version": "1.0.3", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple118/v4/d6/65/04/d66504d2-cde6-9e4f-539b-ed1cd6396003/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple118/v4/81/b0/da/81b0da6c-f104-4c1f-6310-ed260f8001e1/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple118/v4/88/16/d5/8816d503-8827-169a-e5ee-604b2fc08ea4/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple118/v4/5f/7d/f5/5f7df55b-0660-0efc-087c-8940fa6db518/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple118/v4/55/55/32/55553228-0460-37bd-92ed-4631c52e9583/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple118/v4/f1/81/0a/f1810a36-68e2-fd1b-31dd-a9f1db88b344/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple118/v4/f1/81/0a/f1810a36-68e2-fd1b-31dd-a9f1db88b344/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple118/v4/f1/81/0a/f1810a36-68e2-fd1b-31dd-a9f1db88b344/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/vm-mobile-team/id646128457?mt=12&uo=4", "kind": "mac-software", "trackId": 1421835215, "trackName": "Checkers ◦", "releaseDate": "2018-08-09T06:24:05Z", "genreIds": [ "6014", "7004", "7012" ], "formattedPrice": "$1.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.11", "primaryGenreName": "Games", "currentVersionReleaseDate": "2018-12-13T19:07:37Z", "releaseNotes": "Bug fixes and performance improvements", "primaryGenreId": 6014, "sellerName": "VM Mobile Team", "currency": "USD", "trackCensoredName": "Checkers ◦", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "36743790", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/checkers/id1421835215?mt=12&uo=4", "trackContentRating": "4+", "description": "Checkers is the timeless game which has delighted young and old alike for thousands of years. At the same time both simple and complex, it has survived despite the popularity of other game genres and is played by thousands of people. \n\nThis powerful app can be enjoyed with family and friends or on your own, and has different settings to suit your preferences. \n\nIts unique, uncluttered design gives the game a realistic look and feel, evoking memories of the checkers you used to play as a child, and thanks to its fast AI you will forget that you’re playing against a program.\n\nKey features:\n- Games against computer (4 levels, color option)\n- 2 players game with time limit\n- Nice look and feel\n- Undo\n- Save/load unfinished game\n- Timer based game\n\nYou will also gain experience points by winning against the AIs (+1 for Easy, +3 for Medium, +5 for Hard, +7 for Very Hard).\n\nA must have if you like strategic board games.", "genres": [ "Games", "Board", "Puzzle" ], "artistId": 646128457, "artistName": "VM Mobile Team", "price": 1.99, "bundleId": "com.vm.osx.checkersus", "version": "127", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/91/8f/e3/918fe3cc-f08b-910e-7cc8-e78fd2f700e1/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/4c/8a/88/4c8a884e-d6c2-4149-0064-aeafe9d05fb7/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/4c/8a/88/4c8a884e-d6c2-4149-0064-aeafe9d05fb7/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/4c/8a/88/4c8a884e-d6c2-4149-0064-aeafe9d05fb7/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/recursive-illusions/id1502073675?mt=12&uo=4", "kind": "mac-software", "trackId": 1502073676, "trackName": "Boss Alert", "releaseDate": "2020-04-18T07:00:00Z", "genreIds": [ "6002", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.14", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2020-04-22T03:57:16Z", "releaseNotes": "Added a fix for SnapCamera.", "primaryGenreId": 6002, "sellerName": "Recursive Illusions LLP", "currency": "USD", "trackCensoredName": "Boss Alert", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "27606231", "sellerUrl": "https://bossalert.app", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/boss-alert/id1502073676?mt=12&uo=4", "trackContentRating": "4+", "description": "Boss Alert uses the webcam to detect if someone is standing behind you. The moment someone walks in behind you, it will automatically open a work related app (eg. Excel, Word). \n\n100% Privacy!\n\n- Boss Alert uses your webcam to keep a watch behind your back.\n- But rest assured, we don't store your video anywhere (Not even on your own device).\n- We don't collect or send any info about which apps or how long do you use them.\nIt will always be like this!", "genres": [ "Utilities", "Productivity" ], "artistId": 1502073675, "artistName": "Recursive Illusions", "price": 0.00, "bundleId": "com.recursiveillusions.bossalert", "version": "1.1", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/81/55/c6/8155c614-6ddc-dd5b-ca40-bfa4689acb30/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/c2/4f/04/c24f0450-b95e-4807-07c8-0d1c27058401/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/aa/e6/24/aae624a4-989e-8cd5-c020-04188adf6dfc/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/db/a0/93/dba093b9-421d-d04c-f2d5-dccad85a7dfb/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/c4/4f/78/c44f7880-0400-313f-2b46-2bd366a41a8b/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/2c/01/05/2c0105a2-8fe5-7e47-fe89-64855de1cbb8/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/6a/3d/4b/6a3d4b7e-e567-4f8b-2db2-1a16ca35da12/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/94/99/15/949915ad-a4b1-47f6-66ab-11c2e3054897/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/7d/42/70/7d42703f-fa1b-8436-e8f6-9297c4e16138/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/d2/fd/fe/d2fdfe77-3210-eae4-8454-d168935eb1cc/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/14/cd/6f/14cd6fac-4bdf-5344-da46-88fd95be187d/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/14/cd/6f/14cd6fac-4bdf-5344-da46-88fd95be187d/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/14/cd/6f/14cd6fac-4bdf-5344-da46-88fd95be187d/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/cogoo-inc/id951657514?mt=12&uo=4", "kind": "mac-software", "trackId": 1447215756, "trackName": "World of Mines!", "releaseDate": "2018-12-22T12:12:25Z", "genreIds": [ "6014", "7012", "7004" ], "formattedPrice": "$1.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.5.0", "primaryGenreName": "Games", "currentVersionReleaseDate": "2019-11-01T01:33:07Z", "releaseNotes": "- PMA Tournament System Open!!\n- Enjoy special nation package.(Spain, Korea, United Kingdom).", "primaryGenreId": 6014, "sellerName": "Cogoo Inc.", "currency": "USD", "trackCensoredName": "World of Mines!", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "36325933", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/world-of-mines/id1447215756?mt=12&uo=4", "trackContentRating": "4+", "description": "World of Mines is a classic, throwback PC game with a World Map twist. \nInspired by the original game written for many computing platforms since the 1960s. \n\nCOGOO INITIATIVE\nTeam Cogoo has created World of Mines as a small and humble campaign to help promote 2025 Completion Challenge and to raise awareness in our community. Whether you have 10 minutes, one hour, or plenty of time, you can help build a world free of land mines and promote the rights of landmine victims too! \n\nTHE WORLD IS STILL IN DANGER OF LANDMINES\nThe use of land mine is controversial because of their potential as indiscriminate weapons. They can remain dangerous many years after a conflict has ended, harming civilians and the economy. 78 countries are contaminated with land mines and 15000+ people are killed every year. Approximately 80% of land mine casualties are civilian, with children as the most affected age group. Most Killings occur in times of peace. \n\nIf you like old classic games such as chess, checkers, backgammon, solitaire, freecell as well as classic puzzle games like sudoku, tetris and crossword puzzles then this game is for you!\n\nGAME PLAY\n-Save the world from landmines by finding bombs\n-Easy to control \n-Best, smoothest, fastest interface of any Mines game\n-First tap luck\n-Flag Mode: Turn on flag mode to mark tiles you think are bombs quickly\n-177 Countries for hours of fun. (Countries and number of bombs don’t necessarily have a meaning)\n-Variety of map sizes and shapes\n\nHOW TO PLAY\nThe objective of the game is to clear a country map containing hidden “mines” or bombs without detonating any of them, with help from clues about the number or neighboring mines in each field. The player is initially presented with a world map. The Player can select and choose to play a specific country by touching the country on the map. The game is played by revealing squares of the grid by touching each square. If a square containing a mine is revealed, the player loses the game. If no mine is revealed, a digit is instead displayed in the square, indicating how many adjacent squares contains mines. If no mines are adjacent, the square becomes blank, and all adjacent squares will be recursively revealed. The player uses this information to deduce the contents of other squares, and may either safely reveal each square or mark the square as containing a mine. \n\nIf you love the game and care to provide feedback, please join our Facebook group and share you thoughts on future improvements.", "genres": [ "Games", "Puzzle", "Board" ], "artistId": 951657514, "artistName": "Cogoo Inc.", "price": 1.99, "bundleId": "com.cogooland.minesweepermac", "version": "1.9.0", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/fe/31/df/fe31df46-a337-8bec-d7e2-b40d2f555d8f/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/bf/0c/df/bf0cdff1-32bc-bbad-054f-58cd41dd1929/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/cf/c9/78/cfc978e8-f95b-0c23-3125-aeb0fdc74a85/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/cf/22/c5/cf22c5dd-3daf-0afa-0388-a3b1856a4ba8/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/fd/6c/a5/fd6ca5f4-2baa-cc0a-dd34-0962273a0307/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/3a/a4/5a/3aa45a85-b305-92b0-59dc-989aa078c24c/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/42/d4/f2/42d4f24e-3031-fb1e-33d6-9a2a6a866b52/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/9d/0d/c2/9d0dc254-4cb7-a464-f76a-4ea62d4d56ad/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/9d/0d/c2/9d0dc254-4cb7-a464-f76a-4ea62d4d56ad/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/9d/0d/c2/9d0dc254-4cb7-a464-f76a-4ea62d4d56ad/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/crystaly-k-k/id380628262?mt=12&uo=4", "kind": "mac-software", "trackId": 1342613155, "trackName": "Quetie for Qiita:Team", "releaseDate": "2019-03-03T21:13:12Z", "genreIds": [ "6026", "6007" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.13", "primaryGenreName": "Developer Tools", "currentVersionReleaseDate": "2019-03-04T00:11:47Z", "releaseNotes": "- Refine default width of content list", "primaryGenreId": 6026, "sellerName": "CRYSTALY, K.K.", "currency": "USD", "trackCensoredName": "Quetie for Qiita:Team", "languageCodesISO2A": [ "EN", "JA" ], "fileSizeBytes": "6666830", "sellerUrl": "https://crystaly.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/quetie-for-qiita-team/id1342613155?mt=12&uo=4", "trackContentRating": "4+", "description": "Quetie is Qiita:Team Viewer for Mac.\n\nOrganized Tags\n・Slash-separated tags will be displayed hierarchically. You can reach the target tags even if your team has many tags.\n\nSearch\n・Search with considering the high relevance and the newness of articles to reach the target.\n\nOpen Quickly\n・Just type a few letters to open articles, tags, and users quickly.\nEven if there are orthographical variants, it suggests some candidates.\n\nMultiple Teams\n・Quetie supports multiple teams so that you can switch teams quickly.\n\nDark Mode\n・It stands beautifully even in Dark Mode of macOS Mojave.\n\nDesigned exclusively for Mac.\n・Drop to Open\n・AirDrop\n・Sharing Menu\n・Touch Bar\n・Customize Toolbar\n・Drag & Drop\n・Keychain\n・Service Menu\n\n--------------------------------------------------\n\n【Subcription】\nQuetie requires a subscription. \n\nDuration and price of each subscription are displayed in Quetie’ storefront, updated at the time of purchase. Payment will be charged to iTunes account at confirmation of purchase. \n\nTerms of Use and Privacy Policy\nhttps://crystaly.com/terms/\nhttps://crystaly.com/privacy/", "genres": [ "Developer Tools", "Productivity" ], "artistId": 380628262, "artistName": "CRYSTALY, K.K.", "price": 0.00, "bundleId": "com.crystaly.qiity", "version": "1.2.1", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/b2/01/17/b2011751-bd84-c2f9-b7aa-f7ff1eb39863/0e4f5b87-361b-4955-a49f-e45a2f92288f_Email_signature.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/86/49/13/86491346-1a25-fc2d-4eb6-56d207f6bdad/565c8234-7a2d-49f1-8d69-53447422e74a_simple_signature.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/34/91/05/34910508-058d-2ccc-05a3-142d4baa5d32/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/34/91/05/34910508-058d-2ccc-05a3-142d4baa5d32/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/34/91/05/34910508-058d-2ccc-05a3-142d4baa5d32/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/gilbert-philippe-andze-tsoungui/id1444832950?mt=12&uo=4", "kind": "mac-software", "trackId": 1444833191, "trackName": "Email Signature Generator", "releaseDate": "2018-12-03T02:43:00Z", "genreIds": [ "6000" ], "formattedPrice": "$4.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10", "primaryGenreName": "Business", "currentVersionReleaseDate": "2020-08-04T19:08:35Z", "releaseNotes": "-app resizing bug fixed\n-design improved\n-new email signature template", "primaryGenreId": 6000, "sellerName": "Gilbert Philippe Andze Tsoungui", "currency": "USD", "trackCensoredName": "Email Signature Generator", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "3344757", "sellerUrl": "https://www.youtube.com/watch?v=7VNcde5-dCE&feature=youtu.be", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/email-signature-generator/id1444833191?mt=12&uo=4", "trackContentRating": "4+", "description": "Building your HTML email signature is as simple as filling a form. It is super simple to use. If you have ever filled an online form, you know how to use this app.\n\nThe app has a simple way to build your email signature; you click your desired icon and then fill the form. \n\nThat how you do to fill your name, contact info, social media link ...\n\nYou can expect the HTML signature code generated by Email Signature Generator to work with all email clients", "genres": [ "Business" ], "artistId": 1444832950, "artistName": "Gilbert Philippe Andze Tsoungui", "price": 4.99, "bundleId": "COM.GILBERT.Email-Signature", "version": "2.0", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/51/2a/47/512a4708-fb83-e3ec-161a-eb2e8d3693cf/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/25/e7/12/25e71218-6b69-067a-5c4e-2651a9ce2862/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/1d/29/4e/1d294e20-0044-eec3-4cf4-807e81fc337c/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/d3/40/29/d3402954-39d9-3d9a-bec8-6efa55b695d6/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/ce/68/b0/ce68b073-4fb0-283e-b7c6-64e546e19681/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/ce/68/b0/ce68b073-4fb0-283e-b7c6-64e546e19681/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/ce/68/b0/ce68b073-4fb0-283e-b7c6-64e546e19681/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/health-science-inc/id1403957258?mt=12&uo=4", "kind": "mac-software", "trackId": 1498500983, "trackName": "LEON - a wellness app", "releaseDate": "2020-04-16T07:00:00Z", "genreIds": [ "6013", "6020" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.15", "primaryGenreName": "Health & Fitness", "currentVersionReleaseDate": "2020-04-16T18:51:42Z", "primaryGenreId": 6013, "sellerName": "HEALTH SCIENCE INC", "currency": "USD", "trackCensoredName": "LEON - a wellness app", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "3413634", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/leon-a-wellness-app/id1498500983?mt=12&uo=4", "trackContentRating": "4+", "description": "LEON gives you access to the best studios and gyms, health risk assessments, wellness recommendations, and a cutting edge gamification engine. Seamlessly book classes, buy memberships, and schedule events.\n\nIf your employer uses LEON, you can easily connect to receive your corporate wellness benefits.", "genres": [ "Health & Fitness", "Medical" ], "artistId": 1403957258, "artistName": "Health Science Inc", "price": 0.00, "bundleId": "maccatalyst.co.myleon.app", "version": "1.6.4", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/0f/07/d9/0f07d94c-3abf-5551-300b-f707258e4a8d/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple128/v4/f4/fd/d9/f4fdd9f6-c1ec-e6ff-eff0-15a95501d8ce/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple128/v4/78/ef/26/78ef2601-6cf0-061a-406a-c9aafc728f78/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple128/v4/19/97/f5/1997f51d-ac6a-bd59-73b7-40553031f5b6/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/0e/80/ef/0e80effc-877d-5f0e-0b0c-20fe888db831/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/0e/80/ef/0e80effc-877d-5f0e-0b0c-20fe888db831/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple128/v4/0e/80/ef/0e80effc-877d-5f0e-0b0c-20fe888db831/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/vm-mobile-team/id646128457?mt=12&uo=4", "kind": "mac-software", "trackId": 1409316060, "trackName": "Chinese Chess Q", "releaseDate": "2018-08-27T05:25:40Z", "genreIds": [ "6014", "6016", "7004", "7012" ], "formattedPrice": "$1.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.11", "primaryGenreName": "Games", "currentVersionReleaseDate": "2018-09-28T22:28:16Z", "releaseNotes": "Bug fixes and performance improvements", "primaryGenreId": 6014, "sellerName": "VM Mobile Team", "currency": "USD", "trackCensoredName": "Chinese Chess Q", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "38509169", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/chinese-chess-q/id1409316060?mt=12&uo=4", "trackContentRating": "4+", "description": "Xiangqi (象棋) also called Chinese Chess, is a strategy board game for two players. It is one of the most popular board games in China and Vietnam, and is in the same family as Western (or international) chess. \n\nThe game represents a battle between two armies, with the object of capturing the enemy's general.\n\nChinese Chess Q brings this great game to your Mac. You can enjoy Chinese Chess in family game time, or you can also enjoy the game by playing with the AIs of varying difficulties. Beware that it is very challenging to beat the best AI in this game!\n\nYou will also gain experience points by winning against the AIs (+1 for Easy, +3 for Medium and +5 for Hard).\n\nFeatures:\n* Undo\n* Save/load unfinished game\n* AIs with 4 levels of difficulties\n* Timer based game", "genres": [ "Games", "Entertainment", "Board", "Puzzle" ], "artistId": 646128457, "artistName": "VM Mobile Team", "price": 1.99, "bundleId": "com.vm.osx.xiangqi", "version": "120", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple124/v4/8e/70/22/8e70229f-a046-3a96-60d0-9de4760d9e87/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/56/bc/02/56bc0215-09d3-e345-7ccb-6e7fb332f7b6/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/57/ee/09/57ee09ff-2a7e-96e0-dfbe-c7c47a0a386c/source/60x60bb.png", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/57/ee/09/57ee09ff-2a7e-96e0-dfbe-c7c47a0a386c/source/512x512bb.png", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/57/ee/09/57ee09ff-2a7e-96e0-dfbe-c7c47a0a386c/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/rene-rosendahl/id652634280?mt=12&uo=4", "kind": "mac-software", "trackId": 1449437805, "trackName": "TeamTimes", "releaseDate": "2019-02-01T14:17:26Z", "genreIds": [ "6007", "6000" ], "formattedPrice": "$2.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.13", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2019-04-21T19:04:14Z", "releaseNotes": "- Meeting duration can now be selected\n- Ability to add contacts from your Mac address book\n- Data is saved automatically at a regular interval\n- Avatar/image can be removed\n- Fixes for sorting and column sizing and tabbing behavior\n- Email address is checked for valid format", "primaryGenreId": 6007, "sellerName": "Rene Rosendahl", "currency": "USD", "trackCensoredName": "TeamTimes", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "4049495", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/teamtimes/id1449437805?mt=12&uo=4", "trackContentRating": "4+", "description": "Are you working with a distributed team? Trouble keeping track of everyone's time zone and scheduling meetings? TeamTimes can help!\n\n- Track all team members with locations.\n- See everyone’s contact info and local time.\n- Find times that work in everyone’s time zones.\n- Schedule meetings right from within the application.\n- Trigger emails to selected team members.\n- Add members right from your address book.", "genres": [ "Productivity", "Business" ], "artistId": 652634280, "artistName": "Rene Rosendahl", "price": 2.99, "bundleId": "Rene-Rosendahl.TeamTimes", "version": "1.0.5", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/a5/11/35/a51135e3-c400-1129-0483-bd9a7cb3d54d/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/55/90/2a/55902aa9-7695-1499-7693-255f6e5343ff/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/55/90/2a/55902aa9-7695-1499-7693-255f6e5343ff/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/55/90/2a/55902aa9-7695-1499-7693-255f6e5343ff/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/naoki-ainoya/id1190031058?mt=12&uo=4", "kind": "mac-software", "trackId": 1489576942, "trackName": "Siro", "releaseDate": "2019-12-01T08:00:00Z", "genreIds": [ "6007", "6012" ], "formattedPrice": "Free", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.14", "primaryGenreName": "Productivity", "currentVersionReleaseDate": "2019-12-03T20:43:22Z", "releaseNotes": "We added Japanese description to AppStore.", "primaryGenreId": 6007, "sellerName": "Naoki AINOYA", "currency": "USD", "trackCensoredName": "Siro", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "5220947", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/siro/id1489576942?mt=12&uo=4", "trackContentRating": "4+", "description": "## Configuration\n\nOn a settings window, please set channel name you'd like to post and your slack legacy token (slack bot token is also available, but message author is marked at bot apps name.) After that, press the save button, and then press message test button to test posting a message to slack\n\n## Usage\n\n- ctrl+shift+M: open a text window\n- write something as you like\n- command+enter: send the text to slack\n- Esc: close text window", "genres": [ "Productivity", "Lifestyle" ], "artistId": 1190031058, "artistName": "Naoki AINOYA", "price": 0.00, "bundleId": "io.ainoya.macos.siro", "version": "0.3", "wrapperType": "software", "userRatingCount": 0 }, { "supportedDevices": [ "iPadMini4Cellular-iPadMini4Cellular", "iPadPro-iPadPro", "iPhone11ProMax-iPhone11ProMax", "iPadSeventhGenCellular-iPadSeventhGenCellular", "iPhone5s-iPhone5s", "iPadSeventhGen-iPadSeventhGen", "MacDesktop-MacDesktop", "iPad611-iPad611", "iPadAir2Cellular-iPadAir2Cellular", "iPadMini4-iPadMini4", "iPadPro97-iPadPro97", "iPad74-iPad74", "iPadProFourthGenCellular-iPadProFourthGenCellular", "iPadAir-iPadAir", "iPad878-iPad878", "iPadAirCellular-iPadAirCellular", "iPodTouchSeventhGen-iPodTouchSeventhGen", "iPhone11Pro-iPhone11Pro", "iPadMini5-iPadMini5", "iPadMini3Cellular-iPadMini3Cellular", "iPhone6-iPhone6", "iPadMini3-iPadMini3", "iPad71-iPad71", "iPadProSecondGen-iPadProSecondGen", "iPadProSecondGenCellular-iPadProSecondGenCellular", "iPadAir2-iPadAir2", "iPhoneXR-iPhoneXR", "iPhoneSE-iPhoneSE", "iPadPro97Cellular-iPadPro97Cellular", "iPad856-iPad856", "iPadAir3-iPadAir3", "iPad834-iPad834", "iPad76-iPad76", "iPhone8Plus-iPhone8Plus", "iPadMiniRetinaCellular-iPadMiniRetinaCellular", "iPhone11-iPhone11", "iPadMiniRetina-iPadMiniRetina", "iPad75-iPad75", "iPadProCellular-iPadProCellular", "iPhone8-iPhone8", "iPhoneX-iPhoneX", "iPad812-iPad812", "iPhone7-iPhone7", "iPhone6sPlus-iPhone6sPlus", "iPhoneSESecondGen-iPhoneSESecondGen", "iPadAir3Cellular-iPadAir3Cellular", "iPhone6s-iPhone6s", "iPhone6Plus-iPhone6Plus", "iPad72-iPad72", "iPhoneXS-iPhoneXS", "iPhoneXSMax-iPhoneXSMax", "iPodTouchSixthGen-iPodTouchSixthGen", "iPad73-iPad73", "iPadMini5Cellular-iPadMini5Cellular", "iPad612-iPad612", "iPhone7Plus-iPhone7Plus", "iPadProFourthGen-iPadProFourthGen" ], "advisories": [], "isGameCenterEnabled": false, "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/6c/19/e0/6c19e0ad-b983-24f9-c4e6-fb937e4b55f4/3074bff5-66df-4e13-86c3-71d626a03317_1.png/392x696bb.png", "https://is5-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/1e/99/c2/1e99c2a5-47b9-1ff5-b30f-808d057010a3/48f4f008-635a-444a-ab0e-dbfeec6ac190_2.png/392x696bb.png", "https://is3-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/20/6c/8e/206c8e5f-360b-0d93-4210-d120ff84fbd4/b083af8f-dd42-469e-b5cd-1b60ef7dbaf6_3.png/392x696bb.png", "https://is5-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/fb/69/0b/fb690b4d-b552-a176-065c-d04cf6fbbdb1/d74695d0-30cd-4328-b978-25f40f6d53ff_4.png/392x696bb.png", "https://is2-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/b0/cf/7f/b0cf7fa9-5c46-f4cd-34d9-6031d6f9138b/8acf8a3f-757f-48b8-83f1-eb4b4e18f87a_5.png/392x696bb.png", "https://is4-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/ba/0e/2c/ba0e2c5d-b498-e1e0-d6d7-d81997358097/85e28b81-8df2-4afe-905e-9eef2010da11_6.png/392x696bb.png" ], "ipadScreenshotUrls": [ "https://is5-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/4e/71/b0/4e71b01d-b604-d77f-4d34-b1492c1df8c6/29e0061d-73b3-4e7c-b457-b974faf03061_1.png/552x414bb.png", "https://is2-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/90/f7/4b/90f74bc2-ceb5-8f8b-aaa3-07c344336209/c14ea6a7-fdc9-437a-9210-218e01b46584_2.png/552x414bb.png", "https://is1-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/c7/a0/e6/c7a0e66e-25b0-821f-5163-eeb677ed1bfd/40b3fc01-7cd9-4a39-9477-2f149c5344af_3.png/552x414bb.png", "https://is1-ssl.mzstatic.com/image/thumb/PurpleSource114/v4/4c/1f/32/4c1f32e4-e12e-b630-5235-fed7cca6c81c/2561c659-ef81-4f78-a422-b387658fbe2a_4.png/552x414bb.png", "https://is3-ssl.mzstatic.com/image/thumb/PurpleSource124/v4/3d/bf/b0/3dbfb05d-904f-8476-d18f-d8c5a3e1ec83/037bb39d-3998-4db5-895f-4094ff686c4e_5.png/552x414bb.png" ], "appletvScreenshotUrls": [], "artworkUrl60": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/c9/02/cd/c902cd7c-9102-1dec-2eb6-ec1fb88df078/source/60x60bb.jpg", "artworkUrl512": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/c9/02/cd/c902cd7c-9102-1dec-2eb6-ec1fb88df078/source/512x512bb.jpg", "artworkUrl100": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/c9/02/cd/c902cd7c-9102-1dec-2eb6-ec1fb88df078/source/100x100bb.jpg", "artistViewUrl": "https://apps.apple.com/us/developer/enric-enrich/id857031188?uo=4", "features": [ "iosUniversal" ], "kind": "software", "trackId": 1473469253, "trackName": "Veern", "releaseDate": "2019-12-18T08:00:00Z", "genreIds": [ "6002", "6000" ], "formattedPrice": "$4.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "13.0", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2020-07-27T17:28:53Z", "releaseNotes": "- You can now mark places as “favorite” to quickly find them in the “Favorites” screen.\n- Veern now also shows you the groups of the Twist teams that you added.", "primaryGenreId": 6002, "sellerName": "Enric Enrich", "currency": "USD", "trackCensoredName": "Veern", "languageCodesISO2A": [ "EN", "ES" ], "fileSizeBytes": "14846976", "sellerUrl": "https://veern.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 5, "userRatingCountForCurrentVersion": 1, "averageUserRating": 5, "trackViewUrl": "https://apps.apple.com/us/app/veern/id1473469253?uo=4", "trackContentRating": "4+", "description": "Veern helps you connect with people around the world.\n\nVeern is divided in two main sections, “Places” and “Teams”:\n\nIn “Places” you can add as many cities as you want to be aware of their current time. If you are logged in to iCloud, all the cities that you add will be synced across your Apple devices.\n\nIn “Teams” you can add up to 10 teams from Twist and Slack. Veern will then fetch your teammates and show you their current time so you can easily schedule meetings or know if it’s the right time to ask them to jump into a video call.", "genres": [ "Utilities", "Business" ], "artistId": 857031188, "artistName": "Enric Enrich", "price": 4.99, "bundleId": "com.veern.Veern", "version": "2020.4", "wrapperType": "software", "userRatingCount": 1 }, { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple117/v4/16/0c/d4/160cd4c0-ddde-9f57-b83c-63d8ae3ac6c0/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/bf/b5/4c/bfb54c56-3299-1ce3-70f4-bb52ea9f917c/source/60x60bb.png", "artworkUrl512": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/bf/b5/4c/bfb54c56-3299-1ce3-70f4-bb52ea9f917c/source/512x512bb.png", "artworkUrl100": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/bf/b5/4c/bfb54c56-3299-1ce3-70f4-bb52ea9f917c/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/lei-liu/id1135200513?mt=12&uo=4", "kind": "mac-software", "trackId": 1230762386, "trackName": "DaysPast", "releaseDate": "2017-04-28T04:01:03Z", "genreIds": [ "6002", "6007" ], "formattedPrice": "$0.99", "isVppDeviceBasedLicensingEnabled": true, "minimumOsVersion": "10.10", "primaryGenreName": "Utilities", "currentVersionReleaseDate": "2020-02-18T02:42:06Z", "releaseNotes": "Update copyright info.", "primaryGenreId": 6002, "sellerName": "Lei Liu", "currency": "USD", "trackCensoredName": "DaysPast", "languageCodesISO2A": [ "EN", "ZH" ], "fileSizeBytes": "1035846", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/dayspast/id1230762386?mt=12&uo=4", "trackContentRating": "4+", "description": "A utility application to show how days pass in year/month.\n\nApp Features: \n- Several styles to choose, also support customized one.\n- Many ways to notify, including date change, Macs wakes up, etc. \n\nWe would love your feedback and comments. Let us know how did you like DaysPast and what changes you want to see in the upcoming updates. We would appreciate it very much.", "genres": [ "Utilities", "Productivity" ], "artistId": 1135200513, "artistName": "Lei Liu", "price": 0.99, "bundleId": "com.100hps.dayspast.mas", "version": "1.0.6", "wrapperType": "software", "userRatingCount": 0 } ] } ================================================ FILE: Tests/MASTests/Resources/things-lookup.json ================================================ { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/36/fe/ff/36feffbc-a07b-e61e-f0e5-88dcc4455871/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/c6/85/09/c68509b2-c2c8-3000-bf85-4ead056b26f3/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/18/42/aa/1842aab5-0500-b08b-b9a5-fc364f83fbdb/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/de/b9/99/deb99962-f1d0-a7ad-0fc8-ef4bf906515b/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/41/70/7d/41707d88-8ba1-5a28-1f2f-0f2e43a73706/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/be/a3/a2/bea3a233-d82f-34bf-b0cd-38f262b04939/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/e5/41/b4/e541b49d-06ed-9ec6-1544-3df88c8dc340/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/8f/08/49/8f0849f4-7d20-567f-47e6-ef1bfb901619/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/7d/74/8a/7d748af9-50fa-e009-39a8-b5eb7774b2be/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/d4/b9/74/d4b974d7-0c4c-1515-49ec-ecedec84c5a0/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/d4/b9/74/d4b974d7-0c4c-1515-49ec-ecedec84c5a0/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple124/v4/d4/b9/74/d4b974d7-0c4c-1515-49ec-ecedec84c5a0/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/tinybop-inc/id682046582?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.15.0", "trackName": "Things That Go Bump", "trackId": 1472954003, "sellerName": "Tinybop Inc.", "price": 0.99, "fileSizeBytes": "12345678", "formattedPrice": "$0.99", "releaseNotes": "* BOOM *, this is a BIG update. The house spawns a game room, complete with video games you can ENTER INTO. It's fun and a little bit weird! Try it! \n»-(¯`·.·´¯)->", "primaryGenreId": 6014, "primaryGenreName": "Games", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2019-10-18T07:00:00Z", "genreIds": [ "6014", "7001", "7009" ], "currentVersionReleaseDate": "2020-03-18T17:39:23Z", "trackCensoredName": "Things That Go Bump", "languageCodesISO2A": [ "EN" ], "sellerUrl": "https://tinybop.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/things-that-go-bump/id1472954003?mt=12&uo=4", "trackContentRating": "4+", "description": "Have you ever heard something go bump in the night? \nPerhaps you’ve caught wind of a spirit or sprite. \nWhen the house is asleep,\nand there’s dark all around, \nspirits from objects awake and abound. \n\nThe spirits are crafty and like to cause trouble. \nThey're called yōkai and together, they double. \nMixing and mashing, they join to fight. \nCan you help them conquer this mysterious night? \n\nPlay with one person, two, three or four. \nFirst you’ll need to escape the dark junk drawer. \n\n. . . . . . . . . . . . . . . . . . . .\nIn Things That go Bump, familiar objects and rooms come to life every night, and nothing looks quite as does in the day. Create your creature, and battle your friends, but beware the house spirits! They can destroy and they can give life. Battle, create, and make your way through the rooms of the house, and slowly you will unravel the secret of Things that Go Bump. \n\nFeatures:\n * Spirits wake up objects and create yōkai (spirit creatures)\n * Combine everyday objects like umbrellas, staplers, cheese graters and more to create everchanging characters \n * Connect to other players via Game Center and face-off against other spirit creatures and house spirits\n * Add or swap objects to give your spirit creature new skills\n * Gain energy by making mischief, defeating other yōkai, and conquering the house spirits\n * Advance through the house (new rooms will be added throughout the year)\n * Test your curiosity and creativity with new challenges in every room\n * Play with 1-4 players across iPads, iPhones, iPods, AppleTVs and Macs\n * Fun and challenging for the whole family\n * Intuitive, safe, hilarious kid-friendly design\n * New levels introduced roughly every 2 months\n * Original artwork by Adrian Fernandez\n * Original sound design\n\nTinybop, Inc. is a Brooklyn-based studio of designers, engineers, and artists. We make toys for tomorrow. We’re all over the internet.\n\n Visit us: www.tinybop.com\n Follow us: twitter.com/tinybop\n Like us: facebook.com/tinybop\n Peek behind the scenes: instagram.com/tinybop\n\nWe love hearing your stories! If you have ideas, or something isn’t working as you expect it to, please contact us: support@tinybop.com.\n\nPsst! It's not Tiny Bop, or Tiny Bob, or Tiny Pop. It's Tinybop. :)", "currency": "USD", "artistId": 682046582, "artistName": "Tinybop Inc.", "genres": [ "Games", "Action", "Family" ], "bundleId": "uikitformac.com.tinybop.thingamabops", "version": "1.3.0", "wrapperType": "software", "userRatingCount": 0 } ================================================ FILE: Tests/MASTests/Resources/things.json ================================================ { "resultCount": 12, "results": [ { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/6f/4c/e5/6f4ce5d6-7caa-d1eb-bbc9-86558e97d2ba/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/92/b4/f8/92b4f8f5-f133-abd8-db17-135ac27bb1fa/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/72/63/63/726363b9-45ff-f93e-975c-fb69836eaf1a/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/29/fa/63/29fa63e3-3cb2-8b8a-8541-31fa9b7ef27f/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/da/17/5f/da175f95-c2cd-e5df-8cbc-d800d6770c64/pr_source.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/f1/82/37/f182376c-4f25-6dbb-c6a8-5e6c1c617620/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/69/3b/12/693b12e6-67d5-8252-7607-3438e420bbaa/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/69/3b/12/693b12e6-67d5-8252-7607-3438e420bbaa/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/69/3b/12/693b12e6-67d5-8252-7607-3438e420bbaa/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/cultured-code-gmbh-co-kg/id284971784?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.13.0", "trackName": "Things 3", "trackId": 904280696, "sellerName": "Cultured Code GmbH & Co. KG", "releaseNotes": "• Moved the database file to a new location (now at /Library/Group Containers/).\n• Increased the clickable area of items in the sidebar.\n• Improved the formatting of years in Japanese.\n• Fixed some crashes that could occur when hitting Cmd+[ or ] in Quick Entry while the When popover was visible.\n• Updated the crash reporter.\n• Some sync improvements.\n\n\nNEW IN 3.12\n\nWe’re excited to release Things 3.12 – a big update for our Watch app!\n\nWe’ve entirely rebuilt its foundation to allow it to sync and operate without your phone being nearby. We’ve also taken this opportunity to add some often-requested features to the app. For more information about this release, please visit our blog: thingsapp.com\n\nThere are no huge changes in this release for Mac, but there’s one great new feature you should know about: you can now edit the Tags or Deadlines of collapsed to-dos – even for multiple to-dos at once – by hitting Cmd+Shift+T or D. It’s super convenient :)", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2017-05-18T16:42:04Z", "genreIds": [ "6007", "6000" ], "formattedPrice": "$49.99", "currentVersionReleaseDate": "2020-08-04T07:57:44Z", "trackCensoredName": "Things 3", "languageCodesISO2A": [ "EN", "FR", "DE", "IT", "JA", "RU", "ZH", "ES", "ZH" ], "fileSizeBytes": "17474797", "sellerUrl": "https://culturedcode.com/things/", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/things-3/id904280696?mt=12&uo=4", "trackContentRating": "4+", "description": "Get things done! The award-winning Things app helps you plan your day, manage your projects, and make real progress toward your goals.\n\nBest of all, it’s easy to use. Within the hour, you’ll have everything off your mind and neatly organized—from routine tasks to your biggest life goals—and you can start focusing on what matters today.\n\n“Things offers the best combination of design and functionality of any app we tested, with nearly all the features of other power user applications and a delightful interface that never gets in the way of your work.”\n—Wirecutter, The New York Times\n\n\nKEY FEATURES\n\n• Your To-Dos\nYour basic building block is the almighty To-Do—each a small step toward a great accomplishment. You can add notes, tag it, schedule it, and break it down into smaller steps.\n\n• Your Projects\nCreate a Project for any big goal, then add the to-dos to reach it. Use headings to structure your list as you outline your plan. There’s also a place to jot down your notes, and a deadline to keep you on schedule.\n\n• Your Areas\nCreate an Area for each sphere of your life, such as Work, Family, Finance, and so on. This keeps everything neatly organized, and helps you see the big picture as you set your plans in motion.\n\n• Your Plan\nEverything on your schedule is neatly laid out in the Today and Upcoming lists, which show your to-dos and calendar events. Each morning, see what you planned for Today and decide what you want to do. The rest is down to you :)\n\n\nMORE THINGS TO LOVE\n\nAs you dive deeper, you’ll find Things packed with helpful features. Here are just a few:\n\n• Reminders — set a time and Things will remind you.\n• Repeaters — automatically repeat to-dos on a schedule you set.\n• This Evening — a special place for your evening plans.\n• Calendar integration — see your events and to-dos together.\n• Tags — categorize your to-dos and quickly filter lists.\n• Quick Entry — create to-dos from anywhere, as soon as the thought hits you.\n• Quick Find — instantly locate to-dos, headings, or tags.\n• Type Travel — jump from list to list with your keyboard; just start typing!\n• Mail to Things — forward an email to Things; now it’s a to-do.\n• And much more!\n\n\nMADE FOR MAC\n\nThings is tailored to the Mac with deep system integrations as well. A great example is Quick Entry with Autofill: a shortcut that grabs content from other apps and adds it to Things for you, such as a link to a website or an email you want to get back to.\n\nYou can also enjoy a beautiful dark mode at sunset, connect your calendars, enable a Things widget, use your Mac’s Touch Bar, import from Reminders—Things can do it all! There’s even AppleScript support if you need powerful automation.\n\n\nSTAY PRODUCTIVE ON THE GO\n\nThings has full-featured apps for iPhone, iPad, and Apple Watch as well (sold separately). All your devices sync seamlessly via our free Things Cloud service. It’s great to have everything at your fingertips when you need it!\n\n\nAWARD-WINNING DESIGN\n\nMade in Stuttgart, with two Apple Design Awards to its name, Things is a fine example of German engineering: designed, not only to look fantastic, but to be perfectly functional as well. Every detail is thoughtfully considered, then polished to perfection.\n\n“It’s like the unicorn of productivity tools: deep enough for serious work, surprisingly easy to use, and gorgeous enough to enjoy staring at.”\n—Apple\n\n\nGET THINGS TODAY\n\nWhatever it is you want to accomplish in life, Things can help you get there. Install the app today and see what you can do!\n\nVisit our website now and get a free 15-day trial for your Mac: thingsapp.com\n\nIf you have any questions, please get in touch. We provide professional support and will be glad to help you!", "currency": "USD", "artistId": 284971784, "artistName": "Cultured Code GmbH & Co. KG", "genres": [ "Productivity", "Business" ], "price": 49.99, "bundleId": "com.culturedcode.ThingsMac", "version": "3.12.6", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/c9/5d/af/c95daf17-c405-56f0-90f5-9411828e44d2/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/94/32/3b/94323b37-f81b-7ba8-a280-b951e7e840de/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/82/f1/35/82f1356d-1e68-8f9d-3967-566e256f9265/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/dc/ad/83/dcad839b-7705-e4c0-180a-2f97cb68054d/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/21/50/6a/21506a09-c48c-d25e-dfa5-c0d6aa4cdd9d/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/09/64/61/096461c1-f392-ec7d-13dd-2caa927d8244/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/09/64/61/096461c1-f392-ec7d-13dd-2caa927d8244/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/09/64/61/096461c1-f392-ec7d-13dd-2caa927d8244/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/appest-limited/id434073155?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.12", "trackName": "TickTick: Things & Tasks To Do", "trackId": 966085870, "sellerName": "Appest Limited", "releaseNotes": "- Bug fixes and improvements.\n\nRecent Updates:\n- Customizable Section in List View.\n- Tag names can be capitalized.\n- The number of Pomos can now be estimated beforehand.\n- Lists under different folders can share the same name.\n- New city themes! Los Angeles and Cairo.\n\nThanks for using TickTick! We'll bring regular updates to give you more pleasant experience with performance and stability.\nWe'll read all reviews in App Store and evaluate your feedbacks carefully. Any issues encountered during the use, you may write to us via Avatar -> Feedback & Suggestions -> Submit feedback, we will get back to you asap.\nTickTick team with love.", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2016-03-04T06:37:31Z", "genreIds": [ "6007" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-08-27T01:27:34Z", "trackCensoredName": "TickTick: Things & Tasks To Do", "languageCodesISO2A": [ "EN", "ZH" ], "fileSizeBytes": "24698702", "sellerUrl": "https://ticktick.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/ticktick-things-tasks-to-do/id966085870?mt=12&uo=4", "trackContentRating": "4+", "description": "Design exclusively for macOS, TickTick is your daily must-have to-do & task list to get all things done.\nTickTick can be accessed on more than 10 different platforms including Mac, iPhone, iPad, Apple Watch which enables you to manage tasks on all your devices/Web.\n\nKey features: \n- Add task via shortcut (Command+Shift+A)\n- Instant reminder\n- Set priority levels to tasks\n- Set flexible recurring tasks \n- Create checklists within tasks \n- Sort tasks by order/date/name/priority \n- Sync all your tasks across all devices \n\nTickTick is free but you can also upgrade to Premium account for full access of premium features for $2.99 a month or $27.99 a year through an auto-renewing subscription.\n\nPremium Features: \n- Grid view and Timeline view of calendar\n- Duration\n- Custom Smart List\n- Description for checklist\n- Reminders for sub-tasks\n- More lists and tasks (299 lists, 999 tasks in each list, 199 subtasks in each task)\n- Add at most 5 reminders to each task\n- Share a task list up to 19 members for better task collaboration\n- Upload up to 99 attachments every day\n\nSubscriptions for Premium account will be charged to your credit card through your iTunes account. Your subscription will automatically renew unless cancelled at least 24-hours before the end of the current period. You will not be able to cancel a subscription during the active period. You can manage your subscriptions in the Account Settings after purchase. \n\nHow TickTick makes you productive: \n- Get all things done \n- Never miss a schedule\n- Make work more productive \n- Keep life on track \n\nConnect with us: \nFacebook: https://www.facebook.com/TickTickApp\nTwitter: https://twitter.com/TickTickTeam @TickTickTeam\nHelp Center: https://help.ticktick.com/\n\nPrivacy Policy: https://www.ticktick.com/about/privacy\nTerms of Use: https://www.ticktick.com/about/tos", "currency": "USD", "artistId": 434073155, "artistName": "Appest Limited", "genres": [ "Productivity" ], "price": 0.00, "bundleId": "com.TickTick.task.mac", "version": "3.7.11", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple1/v4/79/5c/63/795c63aa-698c-1c6c-b6da-e7ebba718d01/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple30/v4/15/4d/40/154d4071-4a6f-dcd7-0d15-2e495f6f4710/mzm.mvtkjcyn.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple2/v4/e0/31/dc/e031dc74-ce06-afe3-fd8e-8693f6c7c50c/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple1/v4/fc/8d/23/fc8d2367-725d-11dd-6da9-816a7780a1d9/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple71/v4/ff/4d/6b/ff4d6b03-2f12-e12d-9bb3-b3607bcd8ad8/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple71/v4/ff/4d/6b/ff4d6b03-2f12-e12d-9bb3-b3607bcd8ad8/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple71/v4/ff/4d/6b/ff4d6b03-2f12-e12d-9bb3-b3607bcd8ad8/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/antlogic/id364746702?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.6", "trackName": "Simple Antnotes", "trackId": 846599902, "sellerName": "Mykola Olshevskyi", "releaseNotes": "- added option to disable gradient background\n- added option to create new notes in bottom left/right corners\n- changed delay for close/options buttons showing\n- some minor compatibility and UI fixes\n- fixed German localisation", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2014-03-28T12:49:14Z", "genreIds": [ "6007", "6002" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2016-09-24T17:06:52Z", "trackCensoredName": "Simple Antnotes", "languageCodesISO2A": [ "EN", "DE", "RU", "UK" ], "fileSizeBytes": "1002100", "sellerUrl": "https://www.antlogic.com/apps/antnotes", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/simple-antnotes/id846599902?mt=12&uo=4", "trackContentRating": "4+", "description": "Antnotes are like paper notes: they are glued to your monitor, but from the other side of the screen.\n\nThis nice and handy application lives in the menu bar for faster access and has the following features:\n\n- customizable background, font and text color\n- pin note to desktop to make it stay atop of other windows\n- translucent notes\n- make new notes by dragging text, images and files to the menu bar icon\n- drag images and sounds to note contents\n- automatically hide notes when inactive\n- quick access via menu bar icon\n- configurable global shortcuts to create new note or show/hide all notes\n- integration with services: create new note from any text in any application\n- snap to screen bounds and other notes\n- archive with all closed notes - do not lose your information by accidentally closing a note\n- smart position choosing for different display configurations\n\nWant more features? Let us know, or check out our Antnotes application!\n\nVisit our support forums: https://www.antlogic.com/forum/", "currency": "USD", "artistId": 364746702, "artistName": "AntLogic", "genres": [ "Productivity", "Utilities" ], "price": 0.00, "bundleId": "ua.com.AntLogic.SimpleAntnotes", "version": "1.6.1", "wrapperType": "software", "userRatingCount": 0 }, { "appletvScreenshotUrls": [], "supportedDevices": [ "iPadMini4-iPadMini4", "iPadProSecondGen-iPadProSecondGen", "iPhone11-iPhone11", "iPad71-iPad71", "iPadMiniRetinaCellular-iPadMiniRetinaCellular", "iPhone8Plus-iPhone8Plus", "iPhone6sPlus-iPhone6sPlus", "iPadMini5-iPadMini5", "iPadProFourthGen-iPadProFourthGen", "iPhoneXS-iPhoneXS", "iPadAir3Cellular-iPadAir3Cellular", "iPadAir3-iPadAir3", "iPadMini4Cellular-iPadMini4Cellular", "iPadProCellular-iPadProCellular", "MacDesktop-MacDesktop", "iPadMini3-iPadMini3", "iPhoneXR-iPhoneXR", "iPhoneSE-iPhoneSE", "iPad611-iPad611", "iPhone7-iPhone7", "iPad73-iPad73", "iPad812-iPad812", "iPadAir2Cellular-iPadAir2Cellular", "iPhoneX-iPhoneX", "iPadMini5Cellular-iPadMini5Cellular", "iPadPro97-iPadPro97", "iPad834-iPad834", "iPadProSecondGenCellular-iPadProSecondGenCellular", "iPhone5s-iPhone5s", "iPad75-iPad75", "iPadMini3Cellular-iPadMini3Cellular", "iPad878-iPad878", "iPhone6-iPhone6", "iPadAir-iPadAir", "iPadPro97Cellular-iPadPro97Cellular", "iPadSeventhGen-iPadSeventhGen", "iPodTouchSixthGen-iPodTouchSixthGen", "iPhoneXSMax-iPhoneXSMax", "iPad612-iPad612", "iPadPro-iPadPro", "iPodTouchSeventhGen-iPodTouchSeventhGen", "iPhone11ProMax-iPhone11ProMax", "iPadMiniRetina-iPadMiniRetina", "iPad76-iPad76", "iPadProFourthGenCellular-iPadProFourthGenCellular", "iPadSeventhGenCellular-iPadSeventhGenCellular", "iPhoneSESecondGen-iPhoneSESecondGen", "iPad74-iPad74", "iPhone6s-iPhone6s", "iPhone7Plus-iPhone7Plus", "iPadAir2-iPadAir2", "iPad72-iPad72", "iPhone6Plus-iPhone6Plus", "iPadAirCellular-iPadAirCellular", "Watch4-Watch4", "iPhone8-iPhone8", "iPad856-iPad856", "iPhone11Pro-iPhone11Pro" ], "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/2b/ce/8f/2bce8ffa-545b-050c-1dd9-2aeef532facd/pr_source.png/406x228bb.png", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/fb/36/14/fb36142e-17ba-fdab-90c6-e8f9d3c080ef/pr_source.png/406x228bb.png", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/4e/e2/de/4ee2de74-d0ef-010b-19f6-63755aa0175c/pr_source.png/406x228bb.png", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/46/8e/bd/468ebdd3-73a9-ec6a-b4da-6931ce887cff/pr_source.png/406x228bb.png", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/a4/9c/fa/a49cfa14-69e0-f1cf-3924-6ff878027b2d/pr_source.png/406x228bb.png", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/c1/94/cc/c194ccb6-c15a-c0a5-47a6-3ddd625fd98d/pr_source.png/406x228bb.png" ], "ipadScreenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/3f/23/5e/3f235e16-c049-8ee8-ebdc-3d52f25f2636/pr_source.png/552x414bb.png", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/80/48/1d/80481dff-e404-721c-920e-4688f860cf27/pr_source.png/552x414bb.png", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/58/a2/c9/58a2c970-1bd3-6f4d-1bdc-502f75faaa6a/pr_source.png/552x414bb.png", "https://is2-ssl.mzstatic.com/image/thumb/Purple123/v4/2c/a6/06/2ca606eb-8b40-219a-34c5-626f79b7e593/pr_source.png/552x414bb.png", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/c7/d7/04/c7d70441-51bd-1417-c7bf-a5d2702380e4/pr_source.png/552x414bb.png", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/87/e0/75/87e075fd-a979-6151-5744-56ab76ac8f18/pr_source.png/552x414bb.png" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b3/ce/e9/b3cee939-9c28-6e05-f600-2e1b9419e0d2/source/60x60bb.jpg", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b3/ce/e9/b3cee939-9c28-6e05-f600-2e1b9419e0d2/source/512x512bb.jpg", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b3/ce/e9/b3cee939-9c28-6e05-f600-2e1b9419e0d2/source/100x100bb.jpg", "artistViewUrl": "https://apps.apple.com/us/developer/volodymyr-yahenskyi/id961335645?uo=4", "isGameCenterEnabled": false, "advisories": [], "features": [ "iosUniversal" ], "kind": "software", "minimumOsVersion": "11.0", "trackName": "Random: Lists & Decision Maker", "trackId": 1128190780, "sellerName": "Volodymyr Yahenskyi", "releaseNotes": "• Fixed crash when adding items to a new list\n• Fixed lists sync on Apple Watch\n\nThanks for using the Random!\nThis release also contains bug fixes and performance improvements.", "primaryGenreId": 6012, "primaryGenreName": "Lifestyle", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2016-07-05T22:00:04Z", "genreIds": [ "6012", "7009", "6014", "7004" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-08-29T19:21:51Z", "trackCensoredName": "Random: Lists & Decision Maker", "languageCodesISO2A": [ "EN", "RU", "UK" ], "fileSizeBytes": "76392448", "sellerUrl": "https://yahenskyi.dev/random/", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 4.6104900000000004212097337585873901844024658203125, "userRatingCountForCurrentVersion": 1525, "averageUserRating": 4.6104900000000004212097337585873901844024658203125, "trackViewUrl": "https://apps.apple.com/us/app/random-lists-decision-maker/id1128190780?uo=4", "trackContentRating": "4+", "description": "Need a random number? Or can’t you decide what to do? Random is a powerful app that will solve all such problems.\n\nFeatures:\n• Number generator (from a range 0 - 999999999)\n• Letter generator\n• Dice roller (roll up to 4 regular dices in one go)\n• A custom item from a list generator\n• Yes or No \n• Coin flipper\n• Card generator\n• Rock-Paper-Scissors\n• Map Point\n\nGenerate a new random number simply by tapping a ​randomize button or by touching the Apple Watch screen. For those who want a bit of additional exercise, shaking your iOS device will also result in a new random response.\n\nUse Force Touch for setting the minimum or maximum values in your Apple Watch app. Same for the number of dices​, cards, and selection of lists.\n\nRandom Premium subscription benefits:\n• Sync: Get access to your data from all your devices.\n• Themes: Customize the app with various themes and background images.\n• No advertising.\n\nIf you decide to get Random Premium subscription, your purchase will be charged to your iTunes account. 1 month costs $2.99 and 1 year costs $11.99. Active subscriptions will be auto-renewed 24 hours before the expiry date. You can manage subscriptions from Account in iTunes after subscribing, you’ll also be able to cancel the auto-renewing subscription from there at any time. Any unused portion of the free trial period will be forfeited if you purchase a subscription to Random Premium before your trial expires.\n\nTerms & Conditions: https://yahenskyi.dev/terms-conditions/\nPrivacy Policy: https://yahenskyi.dev/privacy-policy/", "currency": "USD", "artistId": 961335645, "artistName": "Volodymyr Yahenskyi", "genres": [ "Lifestyle", "Family", "Games", "Board" ], "price": 0.00, "bundleId": "com.yahenskyi.random", "version": "2.2.10", "wrapperType": "software", "userRatingCount": 1525 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple71/v4/0e/74/bb/0e74bb9a-5ac2-5d5f-516a-5f1c12e95328/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple71/v4/36/54/f0/3654f064-4013-95e6-2683-c89ab8e51102/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple71/v4/1a/75/86/1a758637-9db5-007c-595b-b724e9083321/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b0/7b/ed/b07bed5e-d977-6655-7a6a-d35a90901fba/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b0/7b/ed/b07bed5e-d977-6655-7a6a-d35a90901fba/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/b0/7b/ed/b07bed5e-d977-6655-7a6a-d35a90901fba/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/any-case-solutions/id1396419026?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.10", "trackName": "Task Planner - To Do List", "trackId": 1063681909, "sellerName": "Any Case Solutions, OOO", "releaseNotes": "We’ve updated the app! In the new version:\n- less bugs;\n- minor changes in the interface;\n- some general improvements.\nYour opinion is important to us! Please, leave your feedback - we will gladly consider all your wishes and suggestions.", "primaryGenreId": 6000, "primaryGenreName": "Business", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2016-01-07T00:04:36Z", "genreIds": [ "6000", "6007" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-07-17T23:48:12Z", "trackCensoredName": "Task Planner - To Do List", "languageCodesISO2A": [ "EN", "FR", "DE", "IT", "JA", "KO", "PT", "RU", "ZH", "ES" ], "fileSizeBytes": "27930644", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/task-planner-to-do-list/id1063681909?mt=12&uo=4", "trackContentRating": "4+", "description": "Plan Your Tasks is a productivity tool that allows you to capture your ideas and duties in one place. \nManage everything you have to do while working with many different tasks!\n\nEasy task management - create, organize, and prioritize tasks;\n- Set notifications;\n- Add comments;\n- Sort tasks by categories;\n- Track due dates.\n\nNew approach to agenda\n- Build-in calendar;\n- Coherent tutorial mode;\n- Magic Trackpad 2 support.\n\nCapture all your flash ideas and duties in the calendar and manage your to dos while working with many tasks more effectively.\n\n\nPrivacy Policy: https://anycasesolutions.com/privacy\nTerms Of Use: https://anycasesolutions.com/tos", "currency": "USD", "artistId": 1396419026, "artistName": "Any Case Solutions", "genres": [ "Business", "Productivity" ], "price": 0.00, "bundleId": "com.newtechnologies.iPlanTasksinapp", "version": "2.1.2", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is3-ssl.mzstatic.com/image/thumb/Purple3/v4/69/a0/58/69a0583d-02fd-1d37-cb33-19b80578e9e5/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple3/v4/09/be/29/09be2981-4d08-a021-423a-29cc212c1b59/pr_source.jpg/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple7/v4/28/5a/f4/285af4d8-37e6-118a-ff28-a4211eeb1122/pr_source.jpg/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple3/v4/50/4d/52/504d520c-4f8c-b011-d1e0-18addb5700a8/pr_source.jpg/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple3/v4/f2/6e/07/f26e0760-efb1-0353-3ebd-9fd2803f4b3d/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is5-ssl.mzstatic.com/image/thumb/Purple69/v4/ac/6e/9a/ac6e9aea-8f4b-66bd-6046-c1735f27806f/source/60x60bb.png", "artworkUrl512": "https://is5-ssl.mzstatic.com/image/thumb/Purple69/v4/ac/6e/9a/ac6e9aea-8f4b-66bd-6046-c1735f27806f/source/512x512bb.png", "artworkUrl100": "https://is5-ssl.mzstatic.com/image/thumb/Purple69/v4/ac/6e/9a/ac6e9aea-8f4b-66bd-6046-c1735f27806f/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/realmac-software/id310591643?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.10", "trackName": "Clear – Tasks, Reminders & To-Do Lists", "trackId": 504544917, "sellerName": "Realmac Software Limited", "releaseNotes": "Thanks for using Clear! Just two small enhancements in today’s update:\n\n- We’ve tweaked (increased) the delay before “Click to Clear” appears.\n- We’ve ensured compatibility with OS X El Capitan.\n\nStay productive, and follow @realmacsoftware on Twitter for the latest news!", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2012-11-08T08:00:00Z", "genreIds": [ "6007", "6012" ], "formattedPrice": "$9.99", "currentVersionReleaseDate": "2015-08-19T14:05:32Z", "trackCensoredName": "Clear – Tasks, Reminders & To-Do Lists", "languageCodesISO2A": [ "EN" ], "fileSizeBytes": "13109875", "sellerUrl": "https://impending.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/clear-tasks-reminders-to-do-lists/id504544917?mt=12&uo=4", "trackContentRating": "4+", "description": "Over 2.5 million people de-clutter their lives with Clear, so stop stalling and start organizing your daily routine.\n\nClear is the revolutionary to-do and reminders app that makes you more productive. Just start typing to add to-dos, and once you start organizing your life with Clear you’ll wonder how you ever managed without it.\n\n- Simple gestural design that allows you to focus on your to-dos. Designed for the Magic Trackpad, but works great with a mouse too!\n- Full keyboard navigation. Just start typing to create to-dos.\n- Use separate lists to organize every aspect of your life.\n- iCloud sync built-in so you can be productive everywhere.\n- Set reminders so you’ll never forget important tasks.\n- Personalize your Clear lists with themes and make them your own.\n- Syncs with Clear for iOS (available separately on the App Store).\n\nClear is built by a small team, dedicated to bringing you frequent free feature updates. We’d love to know how we can make you even more productive, so get in touch via the App Store “Support” link, or tweet us @UseClear.\n\nClear for Mac and Clear for iOS are not affiliated with or endorsed by CLEAR Wireless.", "currency": "USD", "artistId": 310591643, "artistName": "Realmac Software", "genres": [ "Productivity", "Lifestyle" ], "price": 9.99, "bundleId": "com.realmacsoftware.clear.mac", "version": "1.1.7", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/cd/bd/44/cdbd44af-06eb-21d6-a793-43dae1077c47/pr_source.jpg/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/e9/8f/17/e98f17c6-787b-b180-6f8d-fb8385ceedd3/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/43/12/a2/4312a25b-f773-9c1a-ddd4-2515d948cc27/pr_source.jpg/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/97/0d/b4/970db444-bbd9-ed77-439e-001bce006e17/pr_source.jpg/800x500bb.jpg" ], "artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/6b/f0/58/6bf058c1-90ab-5bdf-7c06-18de305efd6d/source/60x60bb.png", "artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/6b/f0/58/6bf058c1-90ab-5bdf-7c06-18de305efd6d/source/512x512bb.png", "artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Purple113/v4/6b/f0/58/6bf058c1-90ab-5bdf-7c06-18de305efd6d/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/shenzhen-tomato-software-technology-co-ltd/id966057212?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.12", "trackName": "Focus To-Do: Pomodoro & Tasks", "trackId": 1258530160, "sellerName": "Shenzhen Tomato Software Technology Co., Ltd.", "releaseNotes": "1.Support new languages\n2.Bug fix", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2017-08-02T03:45:26Z", "genreIds": [ "6007", "6002" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-05-03T04:38:29Z", "trackCensoredName": "Focus To-Do: Pomodoro & Tasks", "languageCodesISO2A": [ "CS", "EN", "FR", "DE", "ID", "IT", "JA", "KO", "PL", "PT", "RO", "RU", "ZH", "ES", "ZH", "TR", "VI" ], "fileSizeBytes": "12135791", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/focus-to-do-pomodoro-tasks/id1258530160?mt=12&uo=4", "trackContentRating": "4+", "description": "Focus To-Do combines Pomodoro Timer with Task Management, it is a science-based app that will motivate you to stay focused and get things done. \n\nIt brings Pomodoro Technique and To Do List into one place, you can capture and organize tasks into your todo lists, start focus timer and focus on work & study, set reminders for important tasks and errands, check the time spent at work. \n\nIt's the ultimate app for managing Tasks, Reminders, Lists, Calendar events, Grocery lists, checklist, helping you focus on work & study and tracking your working hours.\n\nFocus To-Do syncs between your phone and computer, so you can access your lists from anywhere.\n\nHow it works:\n 1. Pick a task you need to accomplish.\n 2. Set a timer for 25 minutes, keep focused and start working.\n 3. When the pomodoro timer rings, take a 5 minute break.\n \nKey Features:\n\n- Pomodoro Timer:Stay focused and get more things done.\n Pause and resume Pomodoro\n Customizable pomodoro/breaks lengths\n Notification before the end of a Pomodoro\n Support for short and long breaks\n Skip a break after the end of a Pomodoro\n Continuous Mode\n \n- Tasks Management: Task Organizer, Schedule Planner, Reminder, Habit Tracker, Time Tracker\n Tasks and projects: Organise your day with Focus To-Do and complete your to do, study, work, homework or housework you need to get done.\n Recurring tasks: Build lasting habits with powerful recurring due dates like \"Every Monday\".\n Reminders: Setting a Reminder ensures you never forget important things ever again, you can set up recurring due dates to remind you each and every time. \n Sub-tasks: Break down your task into smaller, actionable items or add a checklist .\n Task Priority: Highlight your day’s most important To-Do with color-coded priority levels.\n Estimated Pomodoro Number: Estimate the workload or set a goal.\n Note: Record more detailed about the task.\n\n- Report: Detailed statistics of your time distribution, tasks completed.\n Support the calculation of the total time of Focus Time.\n Gantt Chart of the Focus Time.\n Statistics on completed To Do. \n Statistics on time distribution of project.\n Trend chart of the completed To Do and the focus time.\n\n- All-Platform synchronization: View and manage your goals wherever you are for better goal achieving.\n Support seamless synchronization within iPhone、Mac、iPad、Apple Watch and other platforms.\n \n- Various Reminding:\n Focus Timer finished alarm, vibration reminding.\n Various white noise to help you focus on work & study.\n\nContact Us: focustodo@163.com, reply within 24 hours.\nWebsite: https://www.focustodo.cn\nPomodoro ™ and Pomodoro Technique ® are registered trademarks of Francesco Cirillo. This app is not affiliated with Francesco Cirillo.\n\nUsers have been focused on our app for 200 million hours, join us and we help you to be focused and increase your productivity, reduce procrastination and anxiety.", "currency": "USD", "artistId": 966057212, "artistName": "Shenzhen Tomato Software Technology Co., Ltd.", "genres": [ "Productivity", "Utilities" ], "price": 0.00, "bundleId": "com.macpomodoro", "version": "6.3", "wrapperType": "software", "userRatingCount": 0 }, { "appletvScreenshotUrls": [], "supportedDevices": [ "iPadMini4-iPadMini4", "iPadProSecondGen-iPadProSecondGen", "iPhone11-iPhone11", "iPad71-iPad71", "iPadMiniRetinaCellular-iPadMiniRetinaCellular", "iPhone8Plus-iPhone8Plus", "iPhone6sPlus-iPhone6sPlus", "iPadMini5-iPadMini5", "iPadProFourthGen-iPadProFourthGen", "iPhoneXS-iPhoneXS", "iPadAir3Cellular-iPadAir3Cellular", "iPadAir3-iPadAir3", "iPadMini4Cellular-iPadMini4Cellular", "iPadProCellular-iPadProCellular", "MacDesktop-MacDesktop", "iPadMini3-iPadMini3", "iPhoneXR-iPhoneXR", "iPhoneSE-iPhoneSE", "iPad611-iPad611", "iPhone7-iPhone7", "iPad73-iPad73", "iPad812-iPad812", "iPadAir2Cellular-iPadAir2Cellular", "iPhoneX-iPhoneX", "iPadMini5Cellular-iPadMini5Cellular", "iPadPro97-iPadPro97", "iPad834-iPad834", "iPadProSecondGenCellular-iPadProSecondGenCellular", "iPhone5s-iPhone5s", "iPad75-iPad75", "iPadMini3Cellular-iPadMini3Cellular", "iPad878-iPad878", "iPhone6-iPhone6", "iPadAir-iPadAir", "iPadPro97Cellular-iPadPro97Cellular", "iPadSeventhGen-iPadSeventhGen", "iPodTouchSixthGen-iPodTouchSixthGen", "iPhoneXSMax-iPhoneXSMax", "iPad612-iPad612", "iPadPro-iPadPro", "iPodTouchSeventhGen-iPodTouchSeventhGen", "iPhone11ProMax-iPhone11ProMax", "iPadMiniRetina-iPadMiniRetina", "iPad76-iPad76", "iPadProFourthGenCellular-iPadProFourthGenCellular", "iPadSeventhGenCellular-iPadSeventhGenCellular", "iPhoneSESecondGen-iPhoneSESecondGen", "iPad74-iPad74", "iPhone6s-iPhone6s", "iPhone7Plus-iPhone7Plus", "iPadAir2-iPadAir2", "iPad72-iPad72", "iPhone6Plus-iPhone6Plus", "iPadAirCellular-iPadAirCellular", "Watch4-Watch4", "iPhone8-iPhone8", "iPad856-iPad856", "iPhone11Pro-iPhone11Pro" ], "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/95/33/5f/95335f94-26d3-3567-93ac-77d60ab821dd/pr_source.png/392x696bb.png", "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/03/53/b9/0353b9b1-ef2a-7ff1-a1b8-4124867af41b/pr_source.png/392x696bb.png", "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/ef/63/ce/ef63ce41-371a-2508-b101-fb99e9c7758f/pr_source.png/392x696bb.png" ], "ipadScreenshotUrls": [ "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/43/19/bb/4319bb4b-5700-0f6b-2c19-7bd386bf186c/pr_source.jpg/552x414bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple114/v4/5d/51/1a/5d511a30-7fab-fd18-6967-c0caf9674d55/pr_source.jpg/552x414bb.jpg" ], "artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/42/50/53/425053d8-2b26-c28a-72db-40323cc62aeb/source/60x60bb.jpg", "artworkUrl512": "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/42/50/53/425053d8-2b26-c28a-72db-40323cc62aeb/source/512x512bb.jpg", "artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/42/50/53/425053d8-2b26-c28a-72db-40323cc62aeb/source/100x100bb.jpg", "artistViewUrl": "https://apps.apple.com/us/developer/kevin-reutter/id1273424431?uo=4", "isGameCenterEnabled": true, "advisories": [], "features": [ "gameCenter", "iosUniversal" ], "kind": "software", "minimumOsVersion": "13.0", "trackName": "Planny 3 - Smart To Do List", "trackId": 1289070327, "sellerName": "Kevin Reutter", "releaseNotes": "Stay tuned! Planny 4 ships in a few week and will be a free update with many great features!\n\n• SwiftUI \nNow Planny uses SwiftUI in some parts of the app. SwiftUI is an innovative, exceptionally simple way to build user interfaces across all Apple platforms with the power of Swift. Over time more and more of the app will be created with SwiftUI to avoid crashes and improve performance. \n\n• Advanced Cursor Support\nWhen using a Trackpad on iPadOS or on the Mac, specific Elements become larger when you come closer to make clicking easier\n\n• Alternative App icons\nChoose the icon color you’d like in settings (iOS for iPhone only)\n\n• New Onboarding Experience\nA new tutorial shows the key features \n\n• New Purchase View\nThe purchase view is now much simpler. Feel free to subscribe :) \n\n• Fixed deadlines on macOS\n• Direct Deadlines now support days and time \n• Fixed issues with overdue tasks \n\nDo you have any wishes for Planny 4? Feel free to submit ideas on the website!", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2017-10-13T19:16:40Z", "genreIds": [ "6007", "6002" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-07-30T17:37:31Z", "trackCensoredName": "Planny 3 - Smart To Do List", "languageCodesISO2A": [ "EN", "FR", "DE", "IT", "RU", "ZH", "ES", "TR" ], "fileSizeBytes": "47687680", "sellerUrl": "https://www.kevinreutter.de/planny-3/", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 4.3897300000000001318767317570745944976806640625, "userRatingCountForCurrentVersion": 331, "averageUserRating": 4.3897300000000001318767317570745944976806640625, "trackViewUrl": "https://apps.apple.com/us/app/planny-3-smart-to-do-list/id1289070327?uo=4", "trackContentRating": "4+", "description": "++ Planny was part of Apples favorite Apps from October ++\n\nPlanny is all new and has been rethought from the ground up.\n\nPlanny is your new friend helping you to be more productive. Planny learned everything important from common to do list apps but combines them with intelligence and gamification. In the morning and during the day Planny intelligently recommends tasks and also reminds you if you tend to forget them. You earn productivity points for adding and completing tasks, and also lose them if you shift tasks or forget them. Users can compare their productivity with friends over the week. \n\nPlanny also features all the important features like deadlines, lists / projects, tagging, location based reminders, notes and attachments, routines and more. \n\nKey features\n• Daily list to focus on today's tasks\n• Assistant for creating a productive daily plan\n• Daily review of the last day\n• Routines to train your habits\n• Deadlines and reminders\n• Smart reminders if you tend to forget your tasks\n• Notes for your tasks\n• Weekly productivity ranking of your contacts\n• Rewards\n• Dark mode\n• Lists\n• Siri support\n• Advanced Apple Watch app\n\nPlanny Premium offers additional features like:\n• Calendar view\n• Teamwork with your friends\n• Add Photos from your library to tasks\n• Add Photos from your camera to tasks\n• Location based reminders\n• iCloud sync\n• iCloud backup \n• FaceID Unlock\n• More than 2 lists\n• Printing\n• Sketches\n• Review your recent days\n• Tagging\n\n+++ Planny Premium - Unlock all features and use Planny on iPhone, iPad and Apple Watch (Mac soon) - And get free feature updates over time! +++ \n\nA Planny Premium subscription unlocks all features. Note that iCloud features require an iCloud-Account. \n\nPlanny offers two auto-renewing subscriptions\n\nPremium 3 Months\n$6,99 / 3 Months (may differ in your country & currency)\n\nPremium Annual\n$19,99 / Year (may differ in your country & currency)\n\nPayment will be charged to iTunes Account at confirmation of purchase\nSubscription automatically renews unless auto-renew is turned off at least 24-hours before the end of the current period\nAccount will be charged for renewal within 24-hours prior to the end of the current period, and identify the cost of the renewal\n\nSubscriptions may be managed by the user and auto-renewal may be turned off by going to the user's Account Settings after purchase\n\nWhen your subscription is cancelled and expires, all the features of Planny Pro won't be available any longer. Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription to that publication, where applicable.\n\nPrivacy policy for Planny: https://kevinreutter.de/privacy\nTerms of use / Conditions: https://kevinreutter.de/privacy", "currency": "USD", "artistId": 1273424431, "artistName": "Kevin Reutter", "genres": [ "Productivity", "Utilities" ], "price": 0.00, "bundleId": "com.kevinreutter.Callisto", "version": "3.4.2", "wrapperType": "software", "userRatingCount": 331 }, { "screenshotUrls": [ "https://is4-ssl.mzstatic.com/image/thumb/Purple/v4/4f/ff/f9/4ffff968-2932-48af-431f-fd1b086026cf/mzl.srudbvwp.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple4/v4/2a/4f/9f/2a4f9fad-c1a7-fa80-56d7-fea2d3beaa0a/mzl.dcyubghz.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple6/v4/56/3a/a7/563aa771-8288-e21a-cfe8-e28e77ffad83/mzl.lzjpfyct.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple4/v4/1e/8d/4e/1e8d4eaa-17f7-5295-1f36-975b62164d19/mzl.yufjavxy.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple4/v4/fb/9b/85/fb9b851d-6ef5-792f-0945-ff2f1a78ce7a/mzl.gycjiioz.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple3/v4/6b/67/f2/6b67f2d4-2603-ec03-504c-fd408d3577d7/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple3/v4/6b/67/f2/6b67f2d4-2603-ec03-504c-fd408d3577d7/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple3/v4/6b/67/f2/6b67f2d4-2603-ec03-504c-fd408d3577d7/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/antlogic/id364746702?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.6.6", "trackName": "To-do Lists", "trackId": 416993121, "sellerName": "Mykola Olshevskyi", "releaseNotes": "fixed accidentally broken compatibility for Mac OS 10.6-10.7", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2011-03-01T03:09:22Z", "genreIds": [ "6007", "6000" ], "formattedPrice": "$4.99", "currentVersionReleaseDate": "2015-04-16T19:07:37Z", "trackCensoredName": "To-do Lists", "languageCodesISO2A": [ "EN", "FR", "DE", "RU", "UK" ], "fileSizeBytes": "2095731", "sellerUrl": "https://www.antlogic.com/#to-do-lists", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/to-do-lists/id416993121?mt=12&uo=4", "trackContentRating": "4+", "description": "To-do Lists provides simple but powerful interface for tasks management.\n\nTo-do Lists features:\n- Quick, one-click tasks addition/removal.\n- Rich-text editing, in-text links support.\n- Seamless iCloud Reminders synchronization.\n- DropBox synchronization between computers and To-do Lists Mobile for iOS\n- Import/export of to-do lists via text files.\n- Printing of to-do lists or mailing them directly from the application.\n- Backup and restore of whole to-do database.\n- Full drag'n'drop support (make new to-do from web link, file, document, e-mail, or any other text by simply dropping them on to-do list).\n- System services support (make new to-do from any text in any application).\n- Rolled-up, translucent or floating to-do lists.\n- Customized background color, text color, font and checkbox appearance.\n- Reminders.\n- Quick-access icon in system menu.\n\nTo-do Lists usage video:\nhttps://www.youtube.com/watch?v=5KB-4sYcelo (or https://www.youtube.com/AntlogicCompany )\n\nFor more information, visit our site at https://www.antlogic.com\nor Facebook page:\nhttps://www.facebook.com/AntlogicCompany\n\nIf you have any problems or questions using To-do Lists - visit our support forums at https://www.antlogic.com/#contact-us", "currency": "USD", "artistId": 364746702, "artistName": "AntLogic", "genres": [ "Productivity", "Business" ], "price": 4.99, "bundleId": "ua.com.AntLogic.ToDoLists", "version": "1.7.7", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/a8/e9/42/a8e942ec-8eea-03b3-ea37-cc6e2837fb5e/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/5f/62/5c/5f625c38-c559-3b8d-5042-94e241735ef1/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/aa/a1/e7/aaa1e746-e660-2b6e-6833-d751e7879752/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple113/v4/c0/6f/7d/c06f7de4-17b9-7475-8778-22a97c13cdce/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/84/3d/8d/843d8de0-6257-7bf0-6a66-6f3ce41af803/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/ad/0d/28/ad0d28c6-ff1d-c394-266a-fdff0b8e9cc6/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/36/28/a3/3628a3e9-6073-0ce4-17d7-9d9a5f479c64/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple113/v4/a9/ac/b9/a9acb983-76e2-d76d-fbc8-c35388dcee48/pr_source.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple113/v4/8f/41/90/8f4190f5-ebb8-370b-c8a6-afb47c56140d/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/09/37/83/09378396-de11-2185-24f4-360be20dbcac/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/71/6f/f0/716ff030-f8ec-536c-41ca-f5116ae1f497/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/71/6f/f0/716ff030-f8ec-536c-41ca-f5116ae1f497/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/71/6f/f0/716ff030-f8ec-536c-41ca-f5116ae1f497/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/the-omni-group/id281731738?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.14", "trackName": "OmniFocus 3", "trackId": 1346203938, "sellerName": "The Omni Group", "releaseNotes": "OmniFocus 3.9.2 is a minor update focused on bug fixes.\n\n• Omni Automation — OmniFocus now recognizes simple plug-ins that use the .omnifocusjs file extention.\n• First Run — Improved reliability of the first run flow.\n• Notice Bar — Fixed bugs related to the Trial Mode & Free Viewer notice bars.\n\nIf you have any feedback or questions, we’d love to hear from you! The Omni Group offers free tech support; you can email omnifocus@omnigroup.com, call 1–800–315–6664 or 1–206–523–4152, or tweet @OmniFocus.\n\nIf OmniFocus empowers you, we would appreciate an App Store review. Your review will help other people find OmniFocus and make them more productive too.", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2018-09-24T12:28:36Z", "genreIds": [ "6007", "6000" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-08-27T17:54:49Z", "trackCensoredName": "OmniFocus 3", "languageCodesISO2A": [ "NL", "EN", "FR", "DE", "IT", "JA", "KO", "PT", "RU", "ZH", "ES" ], "fileSizeBytes": "64931473", "sellerUrl": "https://www.omnigroup.com/omnifocus/", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/omnifocus-3/id1346203938?mt=12&uo=4", "trackContentRating": "4+", "description": "Two-week free trial! OmniFocus Standard and Pro are in-app purchases, with discounts for people who bought earlier versions of OmniFocus for Mac through the Mac App Store. Or you can get OmniFocus for iOS, Mac, and web for just one price with the OmniFocus Subscription. Download the app for details.\n\nUse OmniFocus to accomplish more every day. Create projects and tasks, organize them with tags, focus on what you can do right now — and get stuff done.\n\nOmniFocus — now celebrating 10 years as the trusted, gold-standard to-do list app — brings unrivaled power and flexibility to your Mac, making it easy to work the way you want to work.\n\nOmniFocus manages everything in your busy life. Use projects to organize tasks naturally, and then add tags to organize across projects. Easily enter tasks when you’re on the go, and process them when you have time. Tap the Forecast view — which shows both tasks and calendar events — to get a handle on your day. Use the Review perspective to keep your projects and tasks on track.\n\nThen let our free syncing system make sure your data is the same on every Mac. (And on OmniFocus for iOS and Web, available separately.) Because your data is encrypted, it’s safe in the cloud.\n\nSTANDARD FEATURES (VIA IN-APP PURCHASE)\n\n• NEW: Tags add a powerful additional organizing tool. Create tags for people, energy levels, priorities, locations, and more.\n• NEW: The Forecast view shows your tasks and calendar events in order, so you can better see what’s coming up in your day.\n• NEW: Enhanced repeating tasks are easier than ever to set up — and they work with real-world examples such as the first weekday of the month.\n• NEW: The Modern, fresh-but-familiar design helps you focus on your content.\n• Inbox is where you quickly add tasks — save them when you think of them, and organize them later.\n• Syncing supports end-to-end encryption so that your data is safe wherever it’s stored, on our server or yours.\n• Notes can be attached to your tasks, so you have all the information you need.\n• Attachments — graphics, video, audio, whatever you want — add richness to your tasks.\n• View Options let you customize each perspective by deciding what it should show and how it should filter your tasks.\n• The Review perspective takes you through your projects and tasks — so you stay on track.\n• OmniFocus Mail Drop adds tasks via email and works with services like IFTTT and Zapier (if you’re using our free syncing server).\n• The Today Widget shows you your most important items — you don’t even have to switch to the app to know what’s up.\n• Support for TaskPaper Text and omnifocus:///add and /paste lets you automate using URLs.\n\nPro features make OmniFocus even more powerful:\n\nPRO FEATURES (VIA IN-APP PURCHASE)\n\n• Custom perspectives help you create new ways to see your data by filtering and grouping projects and tags. NEW: The filtering rules are simpler to use while being more powerful than ever, letting you combine rules with “all,” “any,” and “none.” You can also choose any image to use as your custom perspective’s icon, and a custom tint color to go with it.\n• NEW: Today’s Forecast can include items with a specific tag, and you can reorder those tasks however you choose, so you can plan your day better.\n• The customizable sidebar lets you organize your perspectives the way you want to, for super-fast access.\n• The Today Widget shows a perspective of your choice in Notification Center.\n• AppleScript support opens up a world of automation, using Apple’s Mac scripting language.\n\nDownload OmniFocus right now and start your free trial! The app includes a manual, and there’s plenty more documentation on the website.\n\nSUPPORT\n\nIf you have feedback or questions, our Support Humans would love to hear from you! Send email to omnifocus@omnigroup.com, call us at at 1-800-315-6664 or +1-206-523-4152, or reach us on Twitter at @omnifocus.\n\n\nSubscription Terms of Service: https://www.omnigroup.com/legal", "currency": "USD", "artistId": 281731738, "artistName": "The Omni Group", "genres": [ "Productivity", "Business" ], "price": 0.00, "bundleId": "com.omnigroup.OmniFocus3.MacAppStore", "version": "3.9.2", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/1a/72/1d/1a721d98-fbc4-ed9e-2aae-ef9d5b538693/pr_source.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/a2/b1/10/a2b110fd-aa90-286a-658b-2abd85bd1c68/mzl.menowpkq.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/26/f3/32/26f3322f-8ef9-6171-2864-715f571300e6/mzl.qxibkqwt.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/a4/1b/12/a41b12dc-6e1d-74ff-7ad3-1d6888c31462/mzl.fdlcjqnh.png/800x500bb.jpg", "https://is2-ssl.mzstatic.com/image/thumb/Purple124/v4/83/83/24/83832412-85d1-78ff-a9ab-86a37b31121d/mzl.ateekpxr.png/800x500bb.jpg", "https://is1-ssl.mzstatic.com/image/thumb/Purple114/v4/e6/7c/c7/e67cc703-d274-a1fc-88af-b5a8ce9cbfd8/mzl.grtjmgef.png/800x500bb.jpg", "https://is4-ssl.mzstatic.com/image/thumb/Purple124/v4/18/a8/f2/18a8f211-61b5-7b7e-b343-784b260de31d/mzl.ulsghntx.png/800x500bb.jpg" ], "artworkUrl60": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/5a/cf/6c/5acf6c83-c496-d5fb-2445-96ef44f13a82/source/60x60bb.png", "artworkUrl512": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/5a/cf/6c/5acf6c83-c496-d5fb-2445-96ef44f13a82/source/512x512bb.png", "artworkUrl100": "https://is2-ssl.mzstatic.com/image/thumb/Purple114/v4/5a/cf/6c/5acf6c83-c496-d5fb-2445-96ef44f13a82/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/masterbuilders/id896347016?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.14", "trackName": "Focus - Time Management", "trackId": 777233759, "sellerName": "Masterbuilders", "releaseNotes": "Subscription status is now properly unlocked on all devices.", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2013-12-19T19:16:50Z", "genreIds": [ "6007", "6017" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-02-12T20:37:50Z", "trackCensoredName": "Focus - Time Management", "languageCodesISO2A": [ "EN", "FR", "DE", "JA", "ZH", "ES" ], "fileSizeBytes": "24637530", "sellerUrl": "https://www.focusapp.io", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/focus-time-management/id777233759?mt=12&uo=4", "trackContentRating": "4+", "description": "Meet Focus: the best time manager for iPhone, iPad, Apple Watch and Mac. Focus is the most elegant and professional way to get more wore done, working in highly efficient work sessions, one task at a time.\n\n“[…] a tool that can genuinely make people more productive\" – MacStories.net\n\n“[…] a must-have for anyone who finds themselves easily getting distracted or forgetting to take occasional breaks.\" – iDownloadBlog.com\n\n======================\nFEATURES\n======================\n\nFOCUS SESSIONS\nFocus Sessions are a highly efficient way to work. Focus for 25 minutes, then take a short break to relax your mind. After four sessions, take a 15 to 20 minute break. This method maximizes energy, stimulates creativity and promotes a sense of achievement.\n\nTASK MANAGER\nFocus includes a lightweight task manager that lets you organize the things you want to work on intuitively. By working on one task at a time, you won’t be distracted and can focus all your attention towards completing that goal. That way you’ll be perfectly organized on your path to success.\n\nIN-DEPTH STATISTICS\nCheck what you’ve already done! Focus keeps track of your work and offers in-depth and motivating statistics. See your daily, weekly and monthly activity so you don’t lose sight of the big picture. \n\nFOCUS EVERYWHERE\nSeamlessly use Focus on your Mac, iPad, iPhone, and Apple Watch. Sync across your devices using iCloud; use Handoff to pick up your current work on another device and get up-to-the-second data with iCloud Push. You can also use the Today widget to quickly glance at your progress, import tasks using the handy Action extension, and more.\n\nFOCUS & APPLE WATCH: A PERFECT FIT\nUsing Focus on your wrist is a natural fit. The independent Apple Watch app is made for for easy and lightweight interactions that lets you control sessions and track your progress throughout the day. With the Focus complication, you can customize your watch face to see your current progress at a glance.\n\nBEAUTIFUL INTERFACE\nThe name says it all: Focus draws your attention to the most important things. It’s designed to be unobtrusive, accessible and easy-to-use. You’ll intuitively master its collection of features just by using them.\n\n======================\nSUBSCRIPTION PRICING\n======================\n\nFocus offers two subscription options: \nFocus Monthly at $4.99/ month \nFocus Yearly at $39.99/ year\n\nThe subscription unlocks all features on all devices (Mac, iPhone, iPad and Apple Watch).\n\nTRY IT FREE \nFocus Monthly comes with a 3-day free trial period, Focus Yearly with a 7-day free trial period. If you cancel before the end of the trial, you will not be charged for the subscription.\n\nSUBSCRIPTION TERMS\nPayment will be charged to your Apple ID account at the confirmation of purchase or after the free trial period if offered. \n\nYou subscription will automatically renew unless it is canceled at least 24 hours before the end of the current period. Your account will be charged 24 hours prior to the end of the current period. \n\nYou can manage and cancel your subscriptions by going to your account settings in the App Store after purchase. Any unused portion of a free trial will be forfeited when you purchase a subscription\n\n======================\nCONTACT\n======================\n\nIf you have any questions or ideas, please write us at hello@masterbuilders.io\n\nTwitter: @focusappio\nhttps://www.masterbuilders.io\n\n\n\nPrivacy Policy: https://www.masterbuilders.io/privacy\nTerms of Service: https://www.masterbuilders.io/terms", "currency": "USD", "artistId": 896347016, "artistName": "Masterbuilders", "genres": [ "Productivity", "Education" ], "price": 0.00, "bundleId": "com.malteundjan.focus-osx", "version": "6.2.3", "wrapperType": "software", "userRatingCount": 0 }, { "screenshotUrls": [ "https://is5-ssl.mzstatic.com/image/thumb/Purple124/v4/35/03/5a/35035a62-e2da-2f4b-6ece-63475bd7cd02/pr_source.png/800x500bb.jpg", "https://is3-ssl.mzstatic.com/image/thumb/Purple124/v4/b5/ac/1f/b5ac1fe2-431d-e45d-63e0-57ddbfbd525f/pr_source.png/800x500bb.jpg", "https://is5-ssl.mzstatic.com/image/thumb/Purple114/v4/6c/18/ee/6c18eeff-ca66-2b01-82f3-f81576336ab7/pr_source.png/800x500bb.jpg" ], "artworkUrl60": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/12/30/fb/1230fb0c-42fd-1a80-9379-29be0ba0f612/source/60x60bb.png", "artworkUrl512": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/12/30/fb/1230fb0c-42fd-1a80-9379-29be0ba0f612/source/512x512bb.png", "artworkUrl100": "https://is3-ssl.mzstatic.com/image/thumb/Purple114/v4/12/30/fb/1230fb0c-42fd-1a80-9379-29be0ba0f612/source/100x100bb.png", "artistViewUrl": "https://apps.apple.com/us/developer/niklas-behrens/id969210609?mt=12&uo=4", "kind": "mac-software", "minimumOsVersion": "10.10", "trackName": "1Focus: Website & App Blocker", "trackId": 969210610, "sellerName": "Niklas Behrens", "releaseNotes": "- Allows updating 1Focus while blocking is active\n- Fixed toolbar overflow on macOS High Sierra\n- Improved status item width\n- Fixed quick start menu starting wrong task\n- Other bug fixes", "primaryGenreId": 6007, "primaryGenreName": "Productivity", "isVppDeviceBasedLicensingEnabled": true, "releaseDate": "2015-03-15T05:54:46Z", "genreIds": [ "6007", "6017" ], "formattedPrice": "Free", "currentVersionReleaseDate": "2020-07-18T23:51:25Z", "trackCensoredName": "1Focus: Website & App Blocker", "languageCodesISO2A": [ "EN", "FR", "DE", "JA", "KO", "RU", "ZH", "ES" ], "fileSizeBytes": "8338821", "sellerUrl": "https://onefocusapp.com", "contentAdvisoryRating": "4+", "averageUserRatingForCurrentVersion": 0, "userRatingCountForCurrentVersion": 0, "averageUserRating": 0, "trackViewUrl": "https://apps.apple.com/us/app/1focus-website-app-blocker/id969210610?mt=12&uo=4", "trackContentRating": "4+", "description": "1Focus creates an oasis for focused work by disabling access to specific websites and apps. Use it to schedule a bit of automated self-restraint when you find yourself clicking away from what really needs to get done. Ideal for students, freelancers and writers.\n\n\"If you find yourself on Facebook or checking your email every five minutes, you need 1Focus.\" – Pagoda Technologies\n\n\"1Focus is one of the best apps for tuning out the diversions that are most distracting for you.\" – Tyler Horvath, CEO of Tyton Media\n\n\nFREE FEATURES\n\n• Block websites in Google Chrome, Safari, Opera, Microsoft Edge and Brave\n• Block apps (e.g. email, games)\n• Block internet access by blocking web browsers\n• You cannot cancel active blocks once you close the 1Focus window\n• Create your own task presets (up to 2)\n• Dark Mode\n\n\n1FOCUS PRO\n\n• Schedule recurring block events (e.g. Mon - Fri)\n• Work break timer\n• Unlimited task presets\n• Block all websites/apps except specific ones\n• Suspend blocking for a limited time\n• Block URL keywords (e.g. *gaming*)\n• Block popular websites by category (e.g. Social Media)\n\nTry it free for 14 days. $1.99/month or $9.99/year after.\n\nPrices may vary by location. Subscriptions are charged to your iTunes Account. They automatically renew unless you cancel them in your Account Settings at least 24 hours before the end of the current period. Your Account is charged for renewal within 24 hours prior to the end of the current period. Terms of use: https://onefocusapp.com/terms\n\n\nCUSTOMER SUPPORT\n\nDo you have any questions or suggestions?\nonefocusapp.com/support", "currency": "USD", "artistId": 969210609, "artistName": "Niklas Behrens", "genres": [ "Productivity", "Education" ], "price": 0.00, "bundleId": "com.onefocusapp.OneFocus", "version": "3.4.4", "wrapperType": "software", "userRatingCount": 0 } ] } ================================================ FILE: Tests/MASTests/Utilities/Consequences.swift ================================================ // // Consequences.swift // mas // // Copyright © 2024 mas-cli. All rights reserved. // internal import Foundation @testable private import mas struct Consequences { let value: Value? let error: (any Error)? let stdout: String let stderr: String init(_ error: (any Error)? = nil, _ stdout: String = "", _ stderr: String = "") where Value == NoValue { self.init(nil, error, stdout, stderr) } init(_ value: Value?, _ error: (any Error)? = nil, _ stdout: String = "", _ stderr: String = "") { self.value = value self.error = error self.stdout = stdout self.stderr = stderr } } extension Consequences: Equatable where Value: Equatable { // swiftlint:disable:this file_types_order static func == (lhs: Self, rhs: Self) -> Bool { guard lhs.value == rhs.value, lhs.stdout == rhs.stdout, lhs.stderr == rhs.stderr else { return false } return switch (lhs.error, rhs.error) { case (nil, nil): true case let (lhsError?, rhsError?): (lhsError as NSError) == (rhsError as NSError) default: false } } } private struct StandardStreamCapture { // swiftlint:disable:this one_declaration_per_file private let encoding: String.Encoding private let outOriginalFD: Int32 private let errOriginalFD: Int32 private let outDuplicateFD: Int32 private let errDuplicateFD: Int32 private let outPipe: Pipe private let errPipe: Pipe init(encoding: String.Encoding) { self.encoding = encoding outOriginalFD = FileHandle.standardOutput.fileDescriptor errOriginalFD = FileHandle.standardError.fileDescriptor outDuplicateFD = dup(outOriginalFD) errDuplicateFD = dup(errOriginalFD) outPipe = Pipe() errPipe = Pipe() dup2(outPipe.fileHandleForWriting.fileDescriptor, outOriginalFD) dup2(errPipe.fileHandleForWriting.fileDescriptor, errOriginalFD) } func consequences(value _: Void, error: (any Error)? = nil) -> Consequences { let (stdout, stderr) = finishAndRead(encoding: encoding) return .init(nil as NoValue?, error, stdout, stderr) } func consequences(value: Value? = nil, error: (any Error)? = nil) -> Consequences { let (stdout, stderr) = finishAndRead(encoding: encoding) return .init(value, error, stdout, stderr) } private func finishAndRead(encoding: String.Encoding) -> (stdout: String, stderr: String) { unsafe fflush(stdout) unsafe fflush(stderr) dup2(outDuplicateFD, outOriginalFD) dup2(errDuplicateFD, errOriginalFD) try? outPipe.fileHandleForWriting.close() try? errPipe.fileHandleForWriting.close() close(outDuplicateFD) close(errDuplicateFD) return (try! outPipe.readToEnd(encoding: encoding) ?? "", try! errPipe.readToEnd(encoding: encoding) ?? "") } // swiftlint:disable:previous force_try } enum NoValue: Equatable { // swiftlint:disable:this one_declaration_per_file // Empty } func consequencesOf(encoding: String.Encoding = .utf8, _ body: @autoclosure () throws -> Void) -> Consequences { // swiftformat:disable:this indent let capture = StandardStreamCapture(encoding: encoding) do { return capture.consequences(value: try body()) } catch { return capture.consequences(error: error) } } func consequencesOf(encoding: String.Encoding = .utf8, _ body: @autoclosure () async throws -> Void) async -> Consequences { // swiftformat:disable:this indent let capture = StandardStreamCapture(encoding: encoding) do { return capture.consequences(value: try await body()) } catch { return capture.consequences(error: error) } } func consequencesOf(encoding: String.Encoding = .utf8, _ body: @autoclosure () throws -> Value?) -> Consequences { // swiftformat:disable:this indent let capture = StandardStreamCapture(encoding: encoding) do { return capture.consequences(value: try body()) } catch { return capture.consequences(error: error) } } func consequencesOf(encoding: String.Encoding = .utf8, _ body: @autoclosure () async throws -> Value?) async -> Consequences { // swiftformat:disable:this indent let capture = StandardStreamCapture(encoding: encoding) do { return capture.consequences(value: try await body()) } catch { return capture.consequences(error: error) } } ================================================ FILE: contrib/completion/mas-completion.bash ================================================ #!/usr/bin/env bash _mas() { local cur prev words cword if declare -F _init_completions >/dev/null 2>&1; then _init_completion else COMPREPLY=() _get_comp_words_by_ref cur prev words cword fi if [[ "${cword}" -eq 1 ]]; then local -r ifs_old="${IFS}" IFS=$'\n' local -a mas_help=($(mas help)) mas_help=("${mas_help[@]:5:${#mas_help[@]}-6}") mas_help=("${mas_help[@]# }") local -a commands=(help) for line in "${mas_help[@]}"; do if [[ ! "${line}" =~ ^\ ]]; then commands+=("${line%% *}") fi done COMPREPLY=($(compgen -W "${commands[*]}" -- "${cur}")) IFS="${ifs_old}" return 0 fi } complete -F _mas mas ================================================ FILE: contrib/completion/mas.fish ================================================ # fish completions for mas function __fish_mas_list_available -d "Lists applications available to install from the App Store" set query (commandline -ct) if set results (command mas search "$query" 2>/dev/null) for res in $results echo "$res" end | string trim --left | string replace -r '\s+' '\t' end end function __fish_mas_list_installed -d "Lists installed applications from the App Store" command mas list 2>/dev/null | string replace -r '\s+' '\t' end function __fish_mas_outdated_installed -d "Lists outdated installed applications from the App Store" command mas outdated 2>/dev/null | string replace -r '\s+' '\t' end # no file completions in mas complete -c mas -f ### account complete -c mas -n "__fish_use_subcommand" -f -a account -d "Output the Apple Account signed in to the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "account" ### config complete -c mas -n "__fish_use_subcommand" -f -a config -d "Output mas config & related system info" complete -c mas -n "__fish_seen_subcommand_from help" -xa "config" complete -c mas -n "__fish_seen_subcommand_from config" -l markdown -d "Output as Markdown" ### get complete -c mas -n "__fish_use_subcommand" -f -a get -d "Get & install free apps from the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "get" ### help complete -c mas -n "__fish_use_subcommand" -f -a help -d "Output general or command-specific help" complete -c mas -n "__fish_seen_subcommand_from help" -xa "help" ### home complete -c mas -n "__fish_use_subcommand" -f -a home -d "Open App Store app pages in the default web browser" complete -c mas -n "__fish_seen_subcommand_from help" -xa "home" complete -c mas -n "__fish_seen_subcommand_from get home install lookup open seller" -xa "(__fish_mas_list_available)" ### install complete -c mas -n "__fish_use_subcommand" -f -a install -d "Install previously gotten apps from the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "install" complete -c mas -n "__fish_seen_subcommand_from install lucky" -l force -d "Force reinstall" ### list complete -c mas -n "__fish_use_subcommand" -f -a list -d "List all apps installed from the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "list" ### lookup complete -c mas -n "__fish_use_subcommand" -f -a lookup -d "Output app information from the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "lookup" ### lucky complete -c mas -n "__fish_use_subcommand" -f -a lucky -d "Install the first app returned from searching the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "lucky" ### open complete -c mas -n "__fish_use_subcommand" -f -a open -d "Open app page in 'App Store.app'" complete -c mas -n "__fish_seen_subcommand_from help" -xa "open" ### outdated complete -c mas -n "__fish_use_subcommand" -f -a outdated -d "List pending app updates from the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "outdated" complete -c mas -n "__fish_seen_subcommand_from outdated" -l verbose -d "Output warnings about app IDs unknown to the App Store" ### reset complete -c mas -n "__fish_use_subcommand" -f -a reset -d "Reset App Store processes & clear cached App Store downloads" complete -c mas -n "__fish_seen_subcommand_from help" -xa "reset" complete -c mas -n "__fish_seen_subcommand_from reset" -l debug -d "Output debug information" ### search complete -c mas -n "__fish_use_subcommand" -f -a search -d "Search for apps in the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "search" complete -c mas -n "__fish_seen_subcommand_from search" -l price -d "Output the price of each app" ### seller complete -c mas -n "__fish_use_subcommand" -f -a seller -d "Open apps' seller pages in the default web browser" complete -c mas -n "__fish_seen_subcommand_from help" -xa "seller" ### signin complete -c mas -n "__fish_use_subcommand" -f -a signin -d "Sign in to an Apple Account in the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "signin" complete -c mas -n "__fish_seen_subcommand_from signin" -l dialog -d "Provide password via graphical dialog" ### signout complete -c mas -n "__fish_use_subcommand" -f -a signout -d "Sign out of the Apple Account currently signed in to the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "signout" ### uninstall complete -c mas -n "__fish_use_subcommand" -f -a uninstall -d "Uninstall apps installed from the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "uninstall" complete -c mas -n "__fish_seen_subcommand_from uninstall" -l dry-run -d "Perform dry run" complete -c mas -n "__fish_seen_subcommand_from uninstall" -x -a "(__fish_mas_list_installed)" ### update complete -c mas -n "__fish_use_subcommand" -f -a update -d "Update outdated apps installed from the App Store" complete -c mas -n "__fish_seen_subcommand_from help" -xa "update" complete -c mas -n "__fish_seen_subcommand_from update" -x -a "(__fish_mas_outdated_installed)" ### version complete -c mas -n "__fish_use_subcommand" -f -a version -d "Output version number" complete -c mas -n "__fish_seen_subcommand_from help" -xa "version"