├── .env.example ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── analysis_options.yaml ├── assets ├── alfredhatcog.png ├── google.png ├── icon.png └── icon_check.png ├── bin ├── main.dart ├── main_helpers.dart └── src │ ├── api │ ├── datetime_unix_timestamp_converter.dart │ ├── order.dart │ ├── sort.dart │ └── stack_exchange_service.dart │ ├── env │ ├── .gitignore │ └── env.dart │ ├── extensions │ ├── string_helpers.dart │ └── unix_date_time_extension.dart │ └── models │ ├── item_list.dart │ ├── question.dart │ └── question.g.dart ├── build.sh ├── build.yaml ├── demo.gif ├── entitlements.plist ├── info.plist ├── pubspec.lock └── pubspec.yaml /.env.example: -------------------------------------------------------------------------------- 1 | APP_VERSION= 2 | GITHUB_REPOSITORY_URL= 3 | STACK_EXCHANGE_API_VERSION= 4 | STACK_EXCHANGE_API_KEY= 5 | STACK_EXCHANGE_CLIENT_ID= 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: techouse 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pub" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build package 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | runs-on: 7 | description: 'The runner type' 8 | required: true 9 | type: string 10 | defaults: 11 | run: 12 | shell: bash 13 | env: 14 | PUB_ENVIRONMENT: bot.github 15 | permissions: read-all 16 | 17 | jobs: 18 | publish: 19 | name: "Build" 20 | runs-on: "${{ inputs.runs-on }}" 21 | environment: build 22 | env: 23 | GITHUB_REPOSITORY_URL: ${{ github.server_url }}/${{ github.repository }} 24 | steps: 25 | - uses: dart-lang/setup-dart@v1 26 | with: 27 | sdk: stable 28 | - id: checkout 29 | uses: actions/checkout@v4 30 | - name: Compare version with ref/tag 31 | if: startsWith(github.ref, 'refs/tags/') 32 | id: compare_version_with_tag 33 | run: | 34 | set -e 35 | VERSION=$(awk '/^version: / {print $2}' pubspec.yaml) 36 | TAG=${GITHUB_REF_NAME#v} 37 | if [[ "$VERSION" != "$TAG" ]]; then 38 | echo "Version in pubspec.yaml ($VERSION) does not match tag ($TAG)" 39 | exit 1 40 | fi 41 | echo "VERSION=$VERSION" >> $GITHUB_ENV 42 | - name: Configure .env file 43 | id: generate_env_file 44 | env: 45 | STACK_EXCHANGE_API_KEY: ${{ vars.STACK_EXCHANGE_API_KEY }} 46 | STACK_EXCHANGE_API_VERSION: ${{ vars.STACK_EXCHANGE_API_VERSION }} 47 | STACK_EXCHANGE_CLIENT_ID: ${{ vars.STACK_EXCHANGE_CLIENT_ID }} 48 | run: | 49 | set -e 50 | mv .env.example .env 51 | sed -i '' "s#APP_VERSION=.*#APP_VERSION=$VERSION#" .env 52 | sed -i '' "s#GITHUB_REPOSITORY_URL=.*#GITHUB_REPOSITORY_URL=$GITHUB_REPOSITORY_URL#" .env 53 | sed -i '' "s#STACK_EXCHANGE_API_VERSION=.*#STACK_EXCHANGE_API_VERSION=$STACK_EXCHANGE_API_VERSION#" .env 54 | sed -i '' "s#STACK_EXCHANGE_API_KEY=.*#STACK_EXCHANGE_API_KEY=$STACK_EXCHANGE_API_KEY#" .env 55 | sed -i '' "s#STACK_EXCHANGE_CLIENT_ID=.*#STACK_EXCHANGE_CLIENT_ID=$STACK_EXCHANGE_CLIENT_ID#" .env 56 | - name: Configure the info.plist 57 | id: info_plist 58 | run: | 59 | set -e 60 | /usr/libexec/PlistBuddy -c "Set :version $VERSION" ./info.plist 61 | /usr/libexec/PlistBuddy -c "Set :webaddress $GITHUB_REPOSITORY_URL" ./info.plist 62 | - name: Install dependencies 63 | id: install_dependencies 64 | run: | 65 | dart pub get 66 | dart pub global activate -sgit https://github.com/techouse/dart_pubspec_licenses_lite 67 | - name: Run Dart code generation 68 | id: generate_code 69 | run: dart run build_runner build --delete-conflicting-outputs 70 | - name: Check formatting 71 | run: dart format --output=none --set-exit-if-changed . 72 | - name: Analyze 73 | run: dart analyze --fatal-infos 74 | - name: Build executable 75 | id: build_executable 76 | run: bash ./build.sh 77 | - name: Install the Apple certificate 78 | id: install_certificate 79 | env: 80 | BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} 81 | P12_PASSWORD: ${{ secrets.P12_PASSWORD }} 82 | KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} 83 | run: | 84 | set -e 85 | CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 86 | KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db 87 | 88 | # import certificate 89 | echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH 90 | 91 | # create temporary keychain 92 | security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH 93 | security set-keychain-settings -lut 21600 $KEYCHAIN_PATH 94 | security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH 95 | 96 | # import certificate to keychain 97 | security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH 98 | security list-keychain -d user -s $KEYCHAIN_PATH 99 | - name: Sign executable 100 | id: sign_executable 101 | env: 102 | BUILD_CERTIFICATE_SHA1: ${{ secrets.BUILD_CERTIFICATE_SHA1 }} 103 | run: | 104 | set -e 105 | BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'print ":bundleid"' info.plist) 106 | codesign \ 107 | --sign="$BUILD_CERTIFICATE_SHA1" \ 108 | --identifier="$BUNDLE_ID" \ 109 | --deep \ 110 | --force \ 111 | --options=runtime \ 112 | --entitlement="./entitlements.plist" \ 113 | --timestamp \ 114 | ./build/dist/workflow 115 | - name: Verify signature 116 | id: verify_executable_signature 117 | env: 118 | TEAM_ID: ${{ secrets.TEAM_ID }} 119 | run: | 120 | set -e 121 | if [[ $(codesign -dv ./build/dist/workflow 2>&1 | awk -F= '/TeamIdentifier/{print $2}') != "$TEAM_ID" ]]; then 122 | echo "The TeamIdentifier in the signature does not match the signing TeamIdentifier." 123 | exit 1 124 | fi 125 | - name: Package executable into ZIP archive 126 | id: zip_executable 127 | run: | 128 | set -e 129 | zip -j ./build/dist/workflow.zip ./build/dist/workflow 130 | - name: Create notarytool Keychain profile 131 | id: create_keychain_profile 132 | env: 133 | APPLE_ID: ${{ secrets.APPLE_ID }} 134 | TEAM_ID: ${{ secrets.TEAM_ID }} 135 | NOTARYTOOL_PASSWORD: ${{ secrets.NOTARYTOOL_PASSWORD }} 136 | NOTARYTOOL_KEYCHAIN_PROFILE: ${{ vars.NOTARYTOOL_KEYCHAIN_PROFILE }} 137 | run: | 138 | set -e 139 | xcrun notarytool \ 140 | store-credentials "$NOTARYTOOL_KEYCHAIN_PROFILE" \ 141 | --apple-id "$APPLE_ID" \ 142 | --team-id "$TEAM_ID" \ 143 | --password "$NOTARYTOOL_PASSWORD" 144 | - name: Notarize executable 145 | id: notarize_executable 146 | env: 147 | NOTARYTOOL_KEYCHAIN_PROFILE: ${{ vars.NOTARYTOOL_KEYCHAIN_PROFILE }} 148 | run: | 149 | set -e 150 | xcrun notarytool \ 151 | submit ./build/dist/workflow.zip \ 152 | --keychain-profile "$NOTARYTOOL_KEYCHAIN_PROFILE" \ 153 | --wait 154 | - name: Delete obsolete ZIP archive 155 | id: delete_zip_archive 156 | run: | 157 | set -e 158 | rm -rf ./build/dist/workflow.zip 159 | - name: Compress artifacts 160 | id: compress_artifacts 161 | env: 162 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 163 | working-directory: build/dist 164 | run: | 165 | set -e 166 | ARTIFACT_NAME=${WORKFLOW_NAME}-v${VERSION}-$(uname -m) 167 | echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV 168 | find . -not -path "./*_cache*" -exec zip --symlinks "../${ARTIFACT_NAME}.zip" {} + 169 | echo "ARTIFACT_PATH=build/${ARTIFACT_NAME}.zip" >> $GITHUB_ENV 170 | - name: Artifact 171 | id: success_artifact 172 | uses: actions/upload-artifact@v4 173 | with: 174 | name: ${{ env.ARTIFACT_NAME }} 175 | path: ${{ env.ARTIFACT_PATH }} 176 | retention-days: 1 177 | - name: Clean up keychain and build directory 178 | id: clean_up 179 | if: ${{ always() }} 180 | run: | 181 | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db 182 | rm -rf $RUNNER_TEMP/build_certificate.p12 183 | rm .env 184 | rm -rf ./build 185 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release package 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+*' 7 | defaults: 8 | run: 9 | shell: bash 10 | env: 11 | PUB_ENVIRONMENT: bot.github 12 | permissions: read-all 13 | 14 | jobs: 15 | build: 16 | name: "Build" 17 | strategy: 18 | matrix: 19 | os: [macos-14, macos-12] 20 | fail-fast: true 21 | uses: ./.github/workflows/build.yml 22 | with: 23 | runs-on: ${{ matrix.os }} 24 | secrets: inherit 25 | github_release: 26 | name: "Github Release" 27 | needs: build 28 | environment: build 29 | runs-on: ubuntu-latest 30 | permissions: 31 | contents: write 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: Read pubspec.yaml version 35 | id: read_pubspec_version 36 | run: | 37 | set -e 38 | VERSION=$(yq -r '.version' pubspec.yaml) 39 | echo "VERSION=$VERSION" >> $GITHUB_ENV 40 | - name: Get arm64 artifact 41 | id: download_arm64_artifact 42 | uses: actions/download-artifact@v4 43 | env: 44 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 45 | ARCH: arm64 46 | with: 47 | name: ${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}-${{ env.ARCH }} 48 | path: ${{ runner.temp }}/download 49 | - name: Get x86_64 artifact 50 | id: download_x86_64_artifact 51 | uses: actions/download-artifact@v4 52 | env: 53 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 54 | ARCH: x86_64 55 | with: 56 | name: ${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}-${{ env.ARCH }} 57 | path: ${{ runner.temp }}/download 58 | - name: Unzip artifacts 59 | env: 60 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 61 | id: unzip_artifacts 62 | working-directory: ${{ runner.temp }}/download 63 | run: | 64 | set -e 65 | for ARCH in "arm64" "x86_64"; do 66 | mkdir -p $ARCH 67 | unzip -q ${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}-${ARCH}.zip -d $ARCH 68 | done 69 | - name: Create Alfred Workflow 70 | id: create_alfred_workflow 71 | env: 72 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 73 | working-directory: ${{ runner.temp }}/download 74 | run: | 75 | set -e 76 | mv x86_64/workflow arm64/workflow_intel 77 | pushd arm64 78 | find . -not -path "./*_cache*" -exec zip --symlinks "../${WORKFLOW_NAME}-v${VERSION}.alfredworkflow" {} + 79 | popd 80 | - name: Release 81 | id: release_workflow 82 | env: 83 | WORKFLOW_NAME: ${{ vars.WORKFLOW_NAME }} 84 | uses: softprops/action-gh-release@v1 85 | with: 86 | files: ${{ runner.temp }}/download/${{ env.WORKFLOW_NAME }}-v${{ env.VERSION }}.alfredworkflow 87 | - name: Clean up 88 | id: clean_up 89 | if: ${{ always() }} 90 | run: | 91 | rm -rf $RUNNER_TEMP/download -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | 8 | # IDE specific 9 | .idea/ 10 | 11 | # macOS specific 12 | .DS_Store 13 | 14 | bin/*_cache/ 15 | sign.sh 16 | 17 | .env -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | - Initial version. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Klemen Tušar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | help: 4 | @printf "%-20s %s\n" "Target" "Description" 5 | @printf "%-20s %s\n" "------" "-----------" 6 | @make -pqR : 2>/dev/null \ 7 | | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \ 8 | | sort \ 9 | | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ 10 | | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' 11 | 12 | analyze: 13 | @# Help: Analyze the project's Dart code. 14 | dart analyze --fatal-infos 15 | 16 | compile: 17 | @# Help: Compile the executable binary 18 | bash ./build.sh 19 | 20 | check_format: 21 | @# Help: Check the formatting of one or more Dart files. 22 | dart format --output=none --set-exit-if-changed . 23 | 24 | check_outdated: 25 | @# Help: Check which of the project's packages are outdated. 26 | dart pub outdated 27 | 28 | check_style: 29 | @# Help: Analyze the project's Dart code and check the formatting one or more Dart files. 30 | make analyze && make check_format 31 | 32 | clean_code_gen: 33 | @# Help: Remove all generated files. 34 | dart run build_runner clean 35 | 36 | code_gen: 37 | @# Help: Run the build system for Dart code generation and modular compilation. 38 | dart run build_runner build --delete-conflicting-outputs 39 | 40 | code_gen_watcher: 41 | @# Help: Run the build system for Dart code generation and modular compilation as a watcher. 42 | dart run build_runner watch --delete-conflicting-outputs 43 | 44 | format: 45 | @# Help: Format one or more Dart files. 46 | dart format . 47 | 48 | install: 49 | @# Help: Install all the project's packages 50 | dart pub get 51 | 52 | sure: 53 | @# Help: Analyze the project's Dart code, check the formatting one or more Dart files and run unit tests for the current project. 54 | make check_style && make tests 55 | 56 | upgrade: 57 | @# Help: Upgrade all the project's packages. 58 | dart pub upgrade 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StackOverflow Search for Alfred 2 | 3 | ![GitHub release](https://img.shields.io/github/release/techouse/alfred-stackoverflow.svg) 4 | ![GitHub All Releases](https://img.shields.io/github/downloads/techouse/alfred-stackoverflow/total.svg) 5 | ![GitHub](https://img.shields.io/github/license/techouse/alfred-stackoverflow.svg) 6 | 7 | Search [StackOverflow](https://stackoverflow.com) questions using [Alfred](https://www.alfredapp.com/). 8 | 9 | Heavily inspired by [deanishe/alfred-stackexchange](https://github.com/deanishe/alfred-stackexchange). :seedling: 10 | 11 | ![demo](demo.gif) 12 | 13 | ## Installation 14 | 15 | 1. [Download the latest version](https://github.com/techouse/alfred-stackoverflow/releases/latest) 16 | 2. Install the workflow by double-clicking the `.alfredworkflow` file 17 | 3. You can add the workflow to a category, then click "Import" to finish importing. You'll now see the workflow listed 18 | in the left sidebar of your Workflows preferences pane. 19 | 20 | ## Usage 21 | 22 | Just type `so` followed by your ``. 23 | 24 | ``` 25 | so how to encode an http query in python 26 | ``` 27 | 28 | Either press `⌘Y` to Quick Look the result, or press `` to open it in your web browser. 29 | 30 | ### Query syntax 31 | 32 | Prefix a word in your `` with `.` (full stop) to indicate that it's a tag, e.g `requests .python` will 33 | search for answers tagged python with the query requests. 34 | 35 | ### Note 36 | 37 | The search uses the [StackExchange Advanced Search API](https://api.stackexchange.com/docs/advanced-search) which has 38 | a [hard limit of 10,000 queries per IP address per day](https://api.stackexchange.com/docs/throttle). 39 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - "**.g.dart" 6 | 7 | linter: 8 | rules: 9 | avoid_print: true 10 | prefer_single_quotes: true 11 | prefer_relative_imports: false 12 | -------------------------------------------------------------------------------- /assets/alfredhatcog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-stackoverflow/8bfe45151c6a920c6e0f998a5620cd0d440de395/assets/alfredhatcog.png -------------------------------------------------------------------------------- /assets/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-stackoverflow/8bfe45151c6a920c6e0f998a5620cd0d440de395/assets/google.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-stackoverflow/8bfe45151c6a920c6e0f998a5620cd0d440de395/assets/icon.png -------------------------------------------------------------------------------- /assets/icon_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-stackoverflow/8bfe45151c6a920c6e0f998a5620cd0d440de395/assets/icon_check.png -------------------------------------------------------------------------------- /bin/main.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: long-method 2 | 3 | import 'dart:io' show exitCode, stdout; 4 | 5 | import 'package:alfred_workflow/alfred_workflow.dart'; 6 | import 'package:args/args.dart'; 7 | import 'package:cli_script/cli_script.dart'; 8 | import 'package:collection/collection.dart'; 9 | import 'package:html_unescape/html_unescape.dart'; 10 | import 'package:http/http.dart' as http show ClientException; 11 | import 'package:stash/stash_api.dart'; 12 | 13 | import 'src/api/stack_exchange_service.dart'; 14 | import 'src/env/env.dart'; 15 | import 'src/extensions/string_helpers.dart'; 16 | import 'src/models/item_list.dart'; 17 | import 'src/models/question.dart'; 18 | 19 | part 'main_helpers.dart'; 20 | 21 | bool _verbose = false; 22 | bool _update = false; 23 | 24 | void main(List arguments) { 25 | wrapMain(() async { 26 | try { 27 | exitCode = 0; 28 | 29 | _workflow.clearItems(); 30 | 31 | final ArgParser parser = ArgParser() 32 | ..addOption('query', abbr: 'q', defaultsTo: '') 33 | ..addFlag('verbose', abbr: 'v', defaultsTo: false) 34 | ..addFlag('update', abbr: 'u', defaultsTo: false); 35 | final ArgResults args = parser.parse(arguments); 36 | 37 | _update = args['update']; 38 | if (_update) { 39 | stdout.writeln('Updating workflow...'); 40 | 41 | return await _updater.update(); 42 | } 43 | 44 | _verbose = args['verbose']; 45 | 46 | final List query = 47 | args['query'].replaceAll(RegExp(r'\s+'), ' ').trim().split(' '); 48 | 49 | final Set tags = (query 50 | .where((String el) => el.startsWith('.')) 51 | .map((String el) => el.substring(1)) 52 | .toList() 53 | ..sort()) 54 | .toSet(); 55 | 56 | final String queryString = query 57 | .whereNot((String el) => el.startsWith('.')) 58 | .join(' ') 59 | .trim() 60 | .toLowerCase(); 61 | 62 | if (_verbose) stdout.writeln('Query: "$queryString"'); 63 | 64 | if (queryString.isEmpty) { 65 | _showPlaceholder(); 66 | } else { 67 | _workflow.cacheKey = tags.isNotEmpty 68 | ? '${queryString}_${tags.join('.').md5hex}' 69 | : queryString; 70 | if (await _workflow.getItems() == null) { 71 | await _performSearch(query: queryString, tags: tags); 72 | } 73 | } 74 | } on FormatException catch (err) { 75 | exitCode = 2; 76 | _workflow.addItem(AlfredItem(title: err.toString())); 77 | } catch (err) { 78 | exitCode = 1; 79 | _workflow.addItem(AlfredItem(title: err.toString())); 80 | if (_verbose) rethrow; 81 | } finally { 82 | if (!_update) { 83 | if (await _updater.updateAvailable()) { 84 | _workflow.run(addToBeginning: updateItem); 85 | } else { 86 | _workflow.run(); 87 | } 88 | } 89 | } 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /bin/main_helpers.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: long-method 2 | 3 | part of 'main.dart'; 4 | 5 | final HtmlUnescape _unescape = HtmlUnescape(); 6 | 7 | final AlfredWorkflow _workflow = AlfredWorkflow( 8 | cache: AlfredCache( 9 | fromEncodable: (Map json) => AlfredItems.fromJson(json), 10 | maxEntries: 10000, 11 | expiryPolicy: const CreatedExpiryPolicy( 12 | Duration(days: 7), 13 | ), 14 | ), 15 | )..disableAlfredSmartResultOrdering = true; 16 | 17 | final AlfredUpdater _updater = AlfredUpdater( 18 | githubRepositoryUrl: Uri.parse(Env.githubRepositoryUrl), 19 | currentVersion: Env.appVersion, 20 | updateInterval: Duration(days: 1), 21 | ); 22 | 23 | const updateItem = AlfredItem( 24 | title: 'Auto-Update available!', 25 | subtitle: 'Press to auto-update to a new version of this workflow.', 26 | arg: 'update:workflow', 27 | match: 28 | 'Auto-Update available! Press to auto-update to a new version of this workflow.', 29 | icon: AlfredItemIcon(path: 'alfredhatcog.png'), 30 | valid: true, 31 | ); 32 | 33 | void _showPlaceholder() { 34 | _workflow.addItem( 35 | const AlfredItem( 36 | title: 'Search StackOverflow for ...', 37 | icon: AlfredItemIcon(path: 'icon.png'), 38 | ), 39 | ); 40 | } 41 | 42 | Future _performSearch({ 43 | required String query, 44 | Set? tags, 45 | }) async { 46 | try { 47 | final ItemList questions = await StackExchangeService.search( 48 | siteId: 'stackoverflow', 49 | query: query, 50 | tagged: tags, 51 | answers: 1, 52 | page: 1, 53 | pageSize: 20, 54 | ); 55 | 56 | if (questions.isNotEmpty) { 57 | _workflow.addItems( 58 | [ 59 | for (final Question question in questions) 60 | AlfredItem( 61 | uid: question.id.toString(), 62 | title: _unescape.convert(question.title), 63 | subtitle: _unescape.convert(question.tags.join(', ')), 64 | arg: question.link.toString(), 65 | match: _unescape.convert( 66 | '${question.title} ${question.tags.join(' ')}', 67 | ), 68 | text: AlfredItemText( 69 | largeType: question.title, 70 | copy: question.link.toString(), 71 | ), 72 | quickLookUrl: question.link.toString(), 73 | icon: AlfredItemIcon( 74 | path: question.isAnswered ? 'icon_check.png' : 'icon.png', 75 | ), 76 | valid: true, 77 | ), 78 | ], 79 | ); 80 | } else { 81 | final Uri url = Uri.https('www.google.com', '/search', {'q': query}); 82 | 83 | _workflow.addItem( 84 | AlfredItem( 85 | title: 'No matching questions found', 86 | subtitle: 'Shall I try and search Google?', 87 | arg: url.toString(), 88 | text: AlfredItemText(copy: url.toString()), 89 | quickLookUrl: url.toString(), 90 | icon: AlfredItemIcon(path: 'google.png'), 91 | valid: true, 92 | ), 93 | ); 94 | } 95 | } on http.ClientException catch (error) { 96 | _workflow.addItem( 97 | AlfredItem( 98 | title: 'StackExchange API Error', 99 | subtitle: error.message, 100 | text: AlfredItemText(copy: error.message), 101 | valid: false, 102 | ), 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /bin/src/api/datetime_unix_timestamp_converter.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import '../extensions/unix_date_time_extension.dart'; 4 | 5 | class DateTimeUnixTimestampConverter implements JsonConverter { 6 | const DateTimeUnixTimestampConverter(); 7 | 8 | static const instance = DateTimeUnixTimestampConverter(); 9 | 10 | @override 11 | DateTime? fromJson(int? unixTimestamp) => unixTimestamp != null 12 | ? DateTime.fromMillisecondsSinceEpoch(unixTimestamp * 1000, isUtc: true) 13 | : null; 14 | 15 | @override 16 | int? toJson(DateTime? dateTime) => dateTime?.secondsSinceEpoch; 17 | } 18 | -------------------------------------------------------------------------------- /bin/src/api/order.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum Order { 4 | @JsonValue('asc') 5 | asc, 6 | @JsonValue('desc') 7 | desc; 8 | 9 | @override 10 | String toString() => name; 11 | } 12 | -------------------------------------------------------------------------------- /bin/src/api/sort.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | enum Sort { 4 | @JsonValue('activity') 5 | activity, // last_activity_date 6 | @JsonValue('creation') 7 | creation, // creation_date 8 | @JsonValue('votes') 9 | votes, // score 10 | @JsonValue('relevance') 11 | relevance; // matches the relevance tab on the site itself 12 | 13 | @override 14 | String toString() => name; 15 | } 16 | -------------------------------------------------------------------------------- /bin/src/api/stack_exchange_service.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: long-parameter-list 2 | 3 | import 'dart:convert' show jsonDecode; 4 | 5 | import 'package:http/http.dart' as http show get, ClientException, Response; 6 | 7 | import '../env/env.dart'; 8 | import '../extensions/unix_date_time_extension.dart'; 9 | import '../models/item_list.dart'; 10 | import '../models/question.dart'; 11 | import 'order.dart'; 12 | import 'sort.dart'; 13 | 14 | class StackExchangeService { 15 | /// Read more https://api.stackexchange.com/docs/advanced-search 16 | static Future> search({ 17 | required String siteId, 18 | required String query, 19 | bool? accepted, 20 | int? answers, 21 | String? body, 22 | bool? closed, 23 | bool? migrated, 24 | bool? notice, 25 | Set? notTagged, 26 | Set? tagged, 27 | String? title, 28 | int? ownerId, 29 | Uri? url, 30 | int? views, 31 | bool? wiki, 32 | DateTime? fromDate, 33 | DateTime? toDate, 34 | int page = 1, 35 | int pageSize = 100, 36 | Sort sort = Sort.relevance, 37 | Order order = Order.desc, 38 | }) => 39 | _search( 40 | { 41 | 'site': siteId, 42 | 'q': query, 43 | 'accepted': accepted, 44 | 'answers': answers, 45 | 'body': body, 46 | 'closed': closed, 47 | 'migrated': migrated, 48 | 'notice': notice, 49 | 'nottagged': notTagged?.join(';'), 50 | 'tagged': tagged?.join(';'), 51 | 'title': title, 52 | 'user': ownerId, 53 | 'url': url, 54 | 'views': views, 55 | 'wiki': wiki, 56 | 'fromdate': fromDate?.secondsSinceEpoch, 57 | 'todate': toDate?.secondsSinceEpoch, 58 | 'page': page, 59 | 'pagesize': pageSize, 60 | 'sort': sort, 61 | 'order': order, 62 | }..removeWhere((String key, dynamic value) => value == null), 63 | ); 64 | 65 | static Future> _search(Map params) async { 66 | final Uri url = Uri.https( 67 | 'api.stackexchange.com', 68 | '${Env.stackExchangeApiVersion}/search/advanced', 69 | { 70 | ...params, 71 | 'key': Env.stackExchangeApiKey, 72 | 'client_id': Env.stackExchangeClientId, 73 | }.map( 74 | (String key, dynamic value) => MapEntry( 75 | key, 76 | value?.toString(), 77 | ), 78 | ), 79 | ); 80 | 81 | final http.Response response = await http.get(url); 82 | 83 | if (response.statusCode >= 200 && response.statusCode < 300) { 84 | if (response.body.isNotEmpty) { 85 | final Map json = jsonDecode(response.body); 86 | 87 | return json.containsKey('items') && json['items'] is Iterable 88 | ? ItemList( 89 | [ 90 | for (final item in json['items'] as List) 91 | Question.fromJson(item as Map), 92 | ], 93 | hasMore: json['has_more'] as bool? ?? false, 94 | quotaMax: json['quota_max'] as int? ?? 0, 95 | quotaRemaining: json['quota_remaining'] as int? ?? 0, 96 | ) 97 | : ItemList.empty(); 98 | } 99 | 100 | return ItemList.empty(); 101 | } else { 102 | if (response.body.isNotEmpty) { 103 | final Map json = jsonDecode(response.body); 104 | throw http.ClientException( 105 | json['error_message'] ?? 'Failed to load data', 106 | url, 107 | ); 108 | } 109 | 110 | throw http.ClientException('Failed to load data', url); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /bin/src/env/.gitignore: -------------------------------------------------------------------------------- 1 | *.g.dart 2 | !.gitignore -------------------------------------------------------------------------------- /bin/src/env/env.dart: -------------------------------------------------------------------------------- 1 | import 'package:envied/envied.dart'; 2 | 3 | part 'env.g.dart'; 4 | 5 | @Envied(path: '.env') 6 | abstract class Env { 7 | @EnviedField(varName: 'APP_VERSION') 8 | static const String appVersion = _Env.appVersion; 9 | 10 | @EnviedField(varName: 'GITHUB_REPOSITORY_URL') 11 | static const String githubRepositoryUrl = _Env.githubRepositoryUrl; 12 | 13 | @EnviedField(varName: 'STACK_EXCHANGE_API_VERSION') 14 | static const String stackExchangeApiVersion = _Env.stackExchangeApiVersion; 15 | 16 | @EnviedField(varName: 'STACK_EXCHANGE_API_KEY', obfuscate: true) 17 | static final String stackExchangeApiKey = _Env.stackExchangeApiKey; 18 | 19 | @EnviedField(varName: 'STACK_EXCHANGE_CLIENT_ID', obfuscate: true) 20 | static final String stackExchangeClientId = _Env.stackExchangeClientId; 21 | } 22 | -------------------------------------------------------------------------------- /bin/src/extensions/string_helpers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show utf8; 2 | 3 | import 'package:crypto/crypto.dart' as crypto show md5; 4 | 5 | extension StringHelpers on String { 6 | String get md5hex => crypto.md5.convert(utf8.encode(this)).toString(); 7 | } 8 | -------------------------------------------------------------------------------- /bin/src/extensions/unix_date_time_extension.dart: -------------------------------------------------------------------------------- 1 | extension UnixDateTimeExtension on DateTime { 2 | int get secondsSinceEpoch => (millisecondsSinceEpoch / 1000).floor(); 3 | } 4 | -------------------------------------------------------------------------------- /bin/src/models/item_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart' show DelegatingList; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class ItemList extends DelegatingList 5 | with EquatableMixin 6 | implements List { 7 | const ItemList( 8 | super.items, { 9 | this.hasMore = false, 10 | this.quotaMax = 0, 11 | this.quotaRemaining = 0, 12 | }); 13 | 14 | final bool hasMore; 15 | final int quotaMax; 16 | final int quotaRemaining; 17 | 18 | factory ItemList.filled( 19 | int length, 20 | E fill, { 21 | bool growable = false, 22 | bool hasMore = false, 23 | int quotaMax = 0, 24 | int quotaRemaining = 0, 25 | }) => 26 | ItemList( 27 | List.filled(length, fill, growable: growable), 28 | hasMore: hasMore, 29 | quotaMax: quotaMax, 30 | quotaRemaining: quotaRemaining, 31 | ); 32 | 33 | factory ItemList.empty({ 34 | bool growable = false, 35 | bool hasMore = false, 36 | int quotaMax = 0, 37 | int quotaRemaining = 0, 38 | }) => 39 | ItemList( 40 | List.empty(growable: growable), 41 | hasMore: hasMore, 42 | quotaMax: quotaMax, 43 | quotaRemaining: quotaRemaining, 44 | ); 45 | 46 | factory ItemList.from( 47 | Iterable elements, { 48 | bool growable = true, 49 | bool hasMore = false, 50 | int quotaMax = 0, 51 | int quotaRemaining = 0, 52 | }) => 53 | ItemList( 54 | List.from(elements, growable: growable), 55 | hasMore: hasMore, 56 | quotaMax: quotaMax, 57 | quotaRemaining: quotaRemaining, 58 | ); 59 | 60 | factory ItemList.of( 61 | Iterable elements, { 62 | bool growable = true, 63 | bool hasMore = false, 64 | int quotaMax = 0, 65 | int quotaRemaining = 0, 66 | }) => 67 | ItemList( 68 | List.of(elements, growable: growable), 69 | hasMore: hasMore, 70 | quotaMax: quotaMax, 71 | quotaRemaining: quotaRemaining, 72 | ); 73 | 74 | factory ItemList.generate( 75 | int length, 76 | E Function(int index) generator, { 77 | bool growable = true, 78 | bool hasMore = false, 79 | int quotaMax = 0, 80 | int quotaRemaining = 0, 81 | }) => 82 | ItemList( 83 | List.generate(length, generator, growable: growable), 84 | hasMore: hasMore, 85 | quotaMax: quotaMax, 86 | quotaRemaining: quotaRemaining, 87 | ); 88 | 89 | factory ItemList.unmodifiable( 90 | Iterable elements, { 91 | bool hasMore = false, 92 | int quotaMax = 0, 93 | int quotaRemaining = 0, 94 | }) => 95 | ItemList( 96 | List.unmodifiable(elements), 97 | hasMore: hasMore, 98 | quotaMax: quotaMax, 99 | quotaRemaining: quotaRemaining, 100 | ); 101 | 102 | static void copyRange( 103 | ItemList target, 104 | int at, 105 | ItemList source, [ 106 | int? start, 107 | int? end, 108 | ]) => 109 | List.copyRange(target, at, source, start, end); 110 | 111 | static void writeIterable( 112 | ItemList target, 113 | int at, 114 | Iterable source, 115 | ) => 116 | List.writeIterable(target, at, source); 117 | 118 | ItemList copyWith({ 119 | List? items, 120 | bool? hasMore, 121 | int? quotaMax, 122 | int? quotaRemaining, 123 | }) => 124 | ItemList( 125 | items ?? toList(), 126 | hasMore: hasMore ?? this.hasMore, 127 | quotaMax: quotaMax ?? this.quotaMax, 128 | quotaRemaining: quotaRemaining ?? this.quotaRemaining, 129 | ); 130 | 131 | @override 132 | List get props => [ 133 | toList(), 134 | hasMore, 135 | quotaMax, 136 | quotaRemaining, 137 | ]; 138 | } 139 | -------------------------------------------------------------------------------- /bin/src/models/question.dart: -------------------------------------------------------------------------------- 1 | import 'package:autoequal/autoequal.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:json_annotation/json_annotation.dart'; 4 | 5 | import '../api/datetime_unix_timestamp_converter.dart'; 6 | 7 | part 'question.g.dart'; 8 | 9 | @autoequal 10 | @JsonSerializable() 11 | @DateTimeUnixTimestampConverter.instance 12 | class Question with EquatableMixin { 13 | const Question({ 14 | required this.id, 15 | required this.title, 16 | required this.link, 17 | required this.isAnswered, 18 | this.acceptedAnswerId, 19 | required this.viewCount, 20 | required this.answerCount, 21 | required this.score, 22 | required this.tags, 23 | this.lastActivityDate, 24 | this.lastEditDate, 25 | this.creationDate, 26 | this.contentLicense, 27 | }); 28 | 29 | @JsonKey(name: 'question_id') 30 | final int id; 31 | final String title; 32 | final Uri link; 33 | final bool isAnswered; 34 | final int? acceptedAnswerId; 35 | final int viewCount; 36 | final int answerCount; 37 | final int score; 38 | final List tags; 39 | final DateTime? lastActivityDate; 40 | final DateTime? lastEditDate; 41 | final DateTime? creationDate; 42 | final String? contentLicense; 43 | 44 | factory Question.fromJson(Map json) => 45 | _$QuestionFromJson(json); 46 | 47 | Map toJson() => _$QuestionToJson(this); 48 | 49 | @override 50 | List get props => _$props; 51 | } 52 | -------------------------------------------------------------------------------- /bin/src/models/question.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'question.dart'; 4 | 5 | // ************************************************************************** 6 | // AutoequalGenerator 7 | // ************************************************************************** 8 | 9 | extension _$QuestionAutoequal on Question { 10 | List get _$props => [ 11 | id, 12 | title, 13 | link, 14 | isAnswered, 15 | acceptedAnswerId, 16 | viewCount, 17 | answerCount, 18 | score, 19 | tags, 20 | lastActivityDate, 21 | lastEditDate, 22 | creationDate, 23 | contentLicense, 24 | ]; 25 | } 26 | 27 | // ************************************************************************** 28 | // JsonSerializableGenerator 29 | // ************************************************************************** 30 | 31 | Question _$QuestionFromJson(Map json) => Question( 32 | id: json['question_id'] as int, 33 | title: json['title'] as String, 34 | link: Uri.parse(json['link'] as String), 35 | isAnswered: json['is_answered'] as bool, 36 | acceptedAnswerId: json['accepted_answer_id'] as int?, 37 | viewCount: json['view_count'] as int, 38 | answerCount: json['answer_count'] as int, 39 | score: json['score'] as int, 40 | tags: (json['tags'] as List).map((e) => e as String).toList(), 41 | lastActivityDate: DateTimeUnixTimestampConverter.instance 42 | .fromJson(json['last_activity_date'] as int?), 43 | lastEditDate: DateTimeUnixTimestampConverter.instance 44 | .fromJson(json['last_edit_date'] as int?), 45 | creationDate: DateTimeUnixTimestampConverter.instance 46 | .fromJson(json['creation_date'] as int?), 47 | contentLicense: json['content_license'] as String?, 48 | ); 49 | 50 | Map _$QuestionToJson(Question instance) => { 51 | 'question_id': instance.id, 52 | 'title': instance.title, 53 | 'link': instance.link.toString(), 54 | 'is_answered': instance.isAnswered, 55 | 'accepted_answer_id': instance.acceptedAnswerId, 56 | 'view_count': instance.viewCount, 57 | 'answer_count': instance.answerCount, 58 | 'score': instance.score, 59 | 'tags': instance.tags, 60 | 'last_activity_date': DateTimeUnixTimestampConverter.instance 61 | .toJson(instance.lastActivityDate), 62 | 'last_edit_date': 63 | DateTimeUnixTimestampConverter.instance.toJson(instance.lastEditDate), 64 | 'creation_date': 65 | DateTimeUnixTimestampConverter.instance.toJson(instance.creationDate), 66 | 'content_license': instance.contentLicense, 67 | }; 68 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Function to build header 4 | oss_header() { 5 | first=$(printf '%.s#' {1..78}) 6 | second=$( 7 | printf '##' 8 | printf '%.s ' {1..74} 9 | printf '##' 10 | ) 11 | third=$( 12 | printf '##' 13 | printf '%.s ' {1..27} 14 | printf 'OPEN-SOURCE LICENSES' 15 | printf '%.s ' {1..27} 16 | printf '##' 17 | ) 18 | printf "\n\n%s\n%s\n%s\n%s\n%s\n\n" "$first" "$second" "$third" "$second" "$first" 19 | } 20 | 21 | if [ -d "build/dist" ]; then 22 | rm -rf build/dist 23 | fi 24 | 25 | if [ -d "build/debug_info" ]; then 26 | rm -rf build/debug_info 27 | fi 28 | 29 | mkdir -p build/dist build/dist/query_cache build/dist/update_cache build/debug_info 30 | cp -r info.plist assets/* LICENSE README.md build/dist 31 | 32 | if command -v dart-pubspec-licenses-lite; then 33 | oss_header >>build/dist/LICENSE 34 | dart-pubspec-licenses-lite --pubspec-lock pubspec.lock >>build/dist/LICENSE 35 | else 36 | echo 'Info: Unable to generate OSS LICENSES. Please install https://github.com/techouse/dart_pubspec_licenses_lite' 37 | fi 38 | 39 | dart compile exe bin/main.dart -o build/dist/workflow -S build/debug_info/workflow 40 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | json_serializable: 5 | options: 6 | field_rename: snake 7 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techouse/alfred-stackoverflow/8bfe45151c6a920c6e0f998a5620cd0d440de395/demo.gif -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | 8 | -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.techouse.alfred-stackoverflow 7 | category 8 | Tools 9 | connections 10 | 11 | 7C25AA90-19A3-4C7B-8F2E-052F69A07B6C 12 | 13 | 14 | destinationuid 15 | 94B452BA-F5A8-4053-BBA7-B65B0E7C5A49 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | vitoclose 21 | 22 | 23 | 24 | 94B452BA-F5A8-4053-BBA7-B65B0E7C5A49 25 | 26 | 27 | destinationuid 28 | E329424F-DBAC-40BB-B4E5-7563618D2915 29 | modifiers 30 | 0 31 | modifiersubtext 32 | 33 | sourceoutputuid 34 | D850B7E1-B6F7-4A5E-8EF6-522CBF4E0A21 35 | vitoclose 36 | 37 | 38 | 39 | destinationuid 40 | 497E54D1-E9ED-41E5-8850-6FDB09B06EFF 41 | modifiers 42 | 0 43 | modifiersubtext 44 | 45 | vitoclose 46 | 47 | 48 | 49 | 50 | createdby 51 | Klemen Tusar 52 | description 53 | StackOverflow Search for Alfred 54 | disabled 55 | 56 | name 57 | StackOverflow 58 | objects 59 | 60 | 61 | config 62 | 63 | concurrently 64 | 65 | escaping 66 | 102 67 | script 68 | [ "$(uname -m)" = "arm64" ] && ./workflow -u || ./workflow_intel -u 69 | scriptargtype 70 | 1 71 | scriptfile 72 | 73 | type 74 | 0 75 | 76 | type 77 | alfred.workflow.action.script 78 | uid 79 | E329424F-DBAC-40BB-B4E5-7563618D2915 80 | version 81 | 2 82 | 83 | 84 | config 85 | 86 | alfredfiltersresults 87 | 88 | alfredfiltersresultsmatchmode 89 | 0 90 | argumenttreatemptyqueryasnil 91 | 92 | argumenttrimmode 93 | 0 94 | argumenttype 95 | 0 96 | escaping 97 | 102 98 | keyword 99 | so 100 | queuedelaycustom 101 | 3 102 | queuedelayimmediatelyinitially 103 | 104 | queuedelaymode 105 | 2 106 | queuemode 107 | 1 108 | runningsubtext 109 | Fetching StackOverflow questions ... 110 | script 111 | [ "$(uname -m)" = "arm64" ] && ./workflow -q "{query}" || ./workflow_intel -q "{query}" 112 | scriptargtype 113 | 0 114 | scriptfile 115 | 116 | subtext 117 | 118 | title 119 | StackOverflow Search for Alfred 120 | type 121 | 0 122 | withspace 123 | 124 | 125 | type 126 | alfred.workflow.input.scriptfilter 127 | uid 128 | 7C25AA90-19A3-4C7B-8F2E-052F69A07B6C 129 | version 130 | 3 131 | 132 | 133 | config 134 | 135 | conditions 136 | 137 | 138 | inputstring 139 | {query} 140 | matchcasesensitive 141 | 142 | matchmode 143 | 0 144 | matchstring 145 | update:workflow 146 | outputlabel 147 | Update Workflow 148 | uid 149 | D850B7E1-B6F7-4A5E-8EF6-522CBF4E0A21 150 | 151 | 152 | elselabel 153 | Open URL 154 | hideelse 155 | 156 | 157 | type 158 | alfred.workflow.utility.conditional 159 | uid 160 | 94B452BA-F5A8-4053-BBA7-B65B0E7C5A49 161 | version 162 | 1 163 | 164 | 165 | config 166 | 167 | browser 168 | 169 | skipqueryencode 170 | 171 | skipvarencode 172 | 173 | spaces 174 | 175 | url 176 | {query} 177 | 178 | type 179 | alfred.workflow.action.openurl 180 | uid 181 | 497E54D1-E9ED-41E5-8850-6FDB09B06EFF 182 | version 183 | 1 184 | 185 | 186 | readme 187 | 188 | uidata 189 | 190 | 497E54D1-E9ED-41E5-8850-6FDB09B06EFF 191 | 192 | xpos 193 | 550 194 | ypos 195 | 325 196 | 197 | 7C25AA90-19A3-4C7B-8F2E-052F69A07B6C 198 | 199 | xpos 200 | 145 201 | ypos 202 | 220 203 | 204 | 94B452BA-F5A8-4053-BBA7-B65B0E7C5A49 205 | 206 | xpos 207 | 350 208 | ypos 209 | 240 210 | 211 | E329424F-DBAC-40BB-B4E5-7563618D2915 212 | 213 | xpos 214 | 545 215 | ypos 216 | 105 217 | 218 | 219 | userconfigurationconfig 220 | 221 | variablesdontexport 222 | 223 | version 224 | 225 | webaddress 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "67.0.0" 12 | alfred_workflow: 13 | dependency: "direct main" 14 | description: 15 | name: alfred_workflow 16 | sha256: "34b02327f124a6bcc306db4d1ed1eeffa7988b8679342471e7a1aae3a8af39fe" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "0.6.1" 20 | analyzer: 21 | dependency: transitive 22 | description: 23 | name: analyzer 24 | sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "6.4.1" 28 | args: 29 | dependency: "direct main" 30 | description: 31 | name: args 32 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.4.2" 36 | async: 37 | dependency: transitive 38 | description: 39 | name: async 40 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.11.0" 44 | autoequal: 45 | dependency: "direct main" 46 | description: 47 | name: autoequal 48 | sha256: "186f99b614d8dd5b5756b26b6208d21ab948d7798ecfdf84669fff18eed7aab2" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "0.9.1" 52 | autoequal_gen: 53 | dependency: "direct dev" 54 | description: 55 | name: autoequal_gen 56 | sha256: dc58737eaec53da235281d95e2d1975dd898bb39d8466562ca4a67f77a9fbfe8 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "0.9.1" 60 | boolean_selector: 61 | dependency: transitive 62 | description: 63 | name: boolean_selector 64 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "2.1.1" 68 | build: 69 | dependency: transitive 70 | description: 71 | name: build 72 | sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "2.4.1" 76 | build_config: 77 | dependency: transitive 78 | description: 79 | name: build_config 80 | sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.1.1" 84 | build_daemon: 85 | dependency: transitive 86 | description: 87 | name: build_daemon 88 | sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "4.0.1" 92 | build_resolvers: 93 | dependency: transitive 94 | description: 95 | name: build_resolvers 96 | sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "2.4.2" 100 | build_runner: 101 | dependency: "direct dev" 102 | description: 103 | name: build_runner 104 | sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "2.4.8" 108 | build_runner_core: 109 | dependency: transitive 110 | description: 111 | name: build_runner_core 112 | sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "7.3.0" 116 | built_collection: 117 | dependency: transitive 118 | description: 119 | name: built_collection 120 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "5.1.1" 124 | built_value: 125 | dependency: transitive 126 | description: 127 | name: built_value 128 | sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "8.9.0" 132 | change_case: 133 | dependency: transitive 134 | description: 135 | name: change_case 136 | sha256: "47c48c36f95f20c6d0ba03efabceff261d05026cca322cc2c4c01c343371b5bb" 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "2.0.1" 140 | charcode: 141 | dependency: transitive 142 | description: 143 | name: charcode 144 | sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "1.3.1" 148 | checked_yaml: 149 | dependency: transitive 150 | description: 151 | name: checked_yaml 152 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "2.0.3" 156 | cli_script: 157 | dependency: "direct main" 158 | description: 159 | name: cli_script 160 | sha256: "4d4d47dcac7f2e6fb68a6b3e806787b031eeb042bc330ba6dd333ad1e7f83252" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "1.0.0" 164 | clock: 165 | dependency: transitive 166 | description: 167 | name: clock 168 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.1.1" 172 | code_builder: 173 | dependency: transitive 174 | description: 175 | name: code_builder 176 | sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "4.10.0" 180 | collection: 181 | dependency: "direct main" 182 | description: 183 | name: collection 184 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "1.18.0" 188 | convert: 189 | dependency: transitive 190 | description: 191 | name: convert 192 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "3.1.1" 196 | copy_with_extension: 197 | dependency: transitive 198 | description: 199 | name: copy_with_extension 200 | sha256: fbcf890b0c34aedf0894f91a11a579994b61b4e04080204656b582708b5b1125 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "5.0.4" 204 | crypto: 205 | dependency: "direct main" 206 | description: 207 | name: crypto 208 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "3.0.3" 212 | dart_style: 213 | dependency: transitive 214 | description: 215 | name: dart_style 216 | sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" 217 | url: "https://pub.dev" 218 | source: hosted 219 | version: "2.3.4" 220 | envied: 221 | dependency: "direct main" 222 | description: 223 | name: envied 224 | sha256: dab29e21452c3d57ec10889d96b06b4a006b01375d4df10b33c9704800c208c4 225 | url: "https://pub.dev" 226 | source: hosted 227 | version: "0.5.3" 228 | envied_generator: 229 | dependency: "direct dev" 230 | description: 231 | name: envied_generator 232 | sha256: b8655d5cb39b4d1d449a79ff6f1367b252c23955ff17ec7c03aacdff938598bd 233 | url: "https://pub.dev" 234 | source: hosted 235 | version: "0.5.3" 236 | equatable: 237 | dependency: "direct main" 238 | description: 239 | name: equatable 240 | sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 241 | url: "https://pub.dev" 242 | source: hosted 243 | version: "2.0.5" 244 | file: 245 | dependency: transitive 246 | description: 247 | name: file 248 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 249 | url: "https://pub.dev" 250 | source: hosted 251 | version: "7.0.0" 252 | fixnum: 253 | dependency: transitive 254 | description: 255 | name: fixnum 256 | sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" 257 | url: "https://pub.dev" 258 | source: hosted 259 | version: "1.1.0" 260 | frontend_server_client: 261 | dependency: transitive 262 | description: 263 | name: frontend_server_client 264 | sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" 265 | url: "https://pub.dev" 266 | source: hosted 267 | version: "3.2.0" 268 | glob: 269 | dependency: transitive 270 | description: 271 | name: glob 272 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 273 | url: "https://pub.dev" 274 | source: hosted 275 | version: "2.1.2" 276 | graphs: 277 | dependency: transitive 278 | description: 279 | name: graphs 280 | sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 281 | url: "https://pub.dev" 282 | source: hosted 283 | version: "2.3.1" 284 | html_unescape: 285 | dependency: "direct main" 286 | description: 287 | name: html_unescape 288 | sha256: "15362d7a18f19d7b742ef8dcb811f5fd2a2df98db9f80ea393c075189e0b61e3" 289 | url: "https://pub.dev" 290 | source: hosted 291 | version: "2.0.0" 292 | http: 293 | dependency: "direct main" 294 | description: 295 | name: http 296 | sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba 297 | url: "https://pub.dev" 298 | source: hosted 299 | version: "1.2.0" 300 | http_multi_server: 301 | dependency: transitive 302 | description: 303 | name: http_multi_server 304 | sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" 305 | url: "https://pub.dev" 306 | source: hosted 307 | version: "3.2.1" 308 | http_parser: 309 | dependency: transitive 310 | description: 311 | name: http_parser 312 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 313 | url: "https://pub.dev" 314 | source: hosted 315 | version: "4.0.2" 316 | io: 317 | dependency: transitive 318 | description: 319 | name: io 320 | sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" 321 | url: "https://pub.dev" 322 | source: hosted 323 | version: "1.0.4" 324 | js: 325 | dependency: transitive 326 | description: 327 | name: js 328 | sha256: "4186c61b32f99e60f011f7160e32c89a758ae9b1d0c6d28e2c02ef0382300e2b" 329 | url: "https://pub.dev" 330 | source: hosted 331 | version: "0.7.0" 332 | json_annotation: 333 | dependency: "direct main" 334 | description: 335 | name: json_annotation 336 | sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 337 | url: "https://pub.dev" 338 | source: hosted 339 | version: "4.8.1" 340 | json_serializable: 341 | dependency: "direct dev" 342 | description: 343 | name: json_serializable 344 | sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 345 | url: "https://pub.dev" 346 | source: hosted 347 | version: "6.7.1" 348 | lints: 349 | dependency: "direct dev" 350 | description: 351 | name: lints 352 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 353 | url: "https://pub.dev" 354 | source: hosted 355 | version: "3.0.0" 356 | logging: 357 | dependency: transitive 358 | description: 359 | name: logging 360 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" 361 | url: "https://pub.dev" 362 | source: hosted 363 | version: "1.2.0" 364 | matcher: 365 | dependency: transitive 366 | description: 367 | name: matcher 368 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 369 | url: "https://pub.dev" 370 | source: hosted 371 | version: "0.12.16+1" 372 | meta: 373 | dependency: transitive 374 | description: 375 | name: meta 376 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 377 | url: "https://pub.dev" 378 | source: hosted 379 | version: "1.11.0" 380 | mime: 381 | dependency: transitive 382 | description: 383 | name: mime 384 | sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" 385 | url: "https://pub.dev" 386 | source: hosted 387 | version: "1.0.5" 388 | package_config: 389 | dependency: transitive 390 | description: 391 | name: package_config 392 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 393 | url: "https://pub.dev" 394 | source: hosted 395 | version: "2.1.0" 396 | path: 397 | dependency: "direct main" 398 | description: 399 | name: path 400 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 401 | url: "https://pub.dev" 402 | source: hosted 403 | version: "1.9.0" 404 | petitparser: 405 | dependency: transitive 406 | description: 407 | name: petitparser 408 | sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 409 | url: "https://pub.dev" 410 | source: hosted 411 | version: "6.0.2" 412 | plist_parser: 413 | dependency: transitive 414 | description: 415 | name: plist_parser 416 | sha256: e2a6f9abfa0c45c0253656b7360abb0dfb84af9937bace74605b93d2aad2bf0c 417 | url: "https://pub.dev" 418 | source: hosted 419 | version: "0.0.11" 420 | pool: 421 | dependency: transitive 422 | description: 423 | name: pool 424 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 425 | url: "https://pub.dev" 426 | source: hosted 427 | version: "1.5.1" 428 | pub_semver: 429 | dependency: transitive 430 | description: 431 | name: pub_semver 432 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 433 | url: "https://pub.dev" 434 | source: hosted 435 | version: "2.1.4" 436 | pubspec_parse: 437 | dependency: transitive 438 | description: 439 | name: pubspec_parse 440 | sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 441 | url: "https://pub.dev" 442 | source: hosted 443 | version: "1.2.3" 444 | recase: 445 | dependency: transitive 446 | description: 447 | name: recase 448 | sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 449 | url: "https://pub.dev" 450 | source: hosted 451 | version: "4.1.0" 452 | shelf: 453 | dependency: transitive 454 | description: 455 | name: shelf 456 | sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 457 | url: "https://pub.dev" 458 | source: hosted 459 | version: "1.4.1" 460 | shelf_web_socket: 461 | dependency: transitive 462 | description: 463 | name: shelf_web_socket 464 | sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" 465 | url: "https://pub.dev" 466 | source: hosted 467 | version: "1.0.4" 468 | source_gen: 469 | dependency: transitive 470 | description: 471 | name: source_gen 472 | sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" 473 | url: "https://pub.dev" 474 | source: hosted 475 | version: "1.5.0" 476 | source_helper: 477 | dependency: transitive 478 | description: 479 | name: source_helper 480 | sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" 481 | url: "https://pub.dev" 482 | source: hosted 483 | version: "1.3.4" 484 | source_span: 485 | dependency: transitive 486 | description: 487 | name: source_span 488 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 489 | url: "https://pub.dev" 490 | source: hosted 491 | version: "1.10.0" 492 | sprintf: 493 | dependency: transitive 494 | description: 495 | name: sprintf 496 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 497 | url: "https://pub.dev" 498 | source: hosted 499 | version: "7.0.0" 500 | stack_trace: 501 | dependency: transitive 502 | description: 503 | name: stack_trace 504 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 505 | url: "https://pub.dev" 506 | source: hosted 507 | version: "1.11.1" 508 | stash: 509 | dependency: "direct main" 510 | description: 511 | name: stash 512 | sha256: "5d42c73727336e9ea8cef3b028355fc4728b4cdad643444d74e432d0289cdb7d" 513 | url: "https://pub.dev" 514 | source: hosted 515 | version: "5.1.0" 516 | stash_file: 517 | dependency: transitive 518 | description: 519 | name: stash_file 520 | sha256: "207cf47bc13dd3a9fd4f2be7a2e6612ad89801c5674f429f50f5e8ecdcb23de0" 521 | url: "https://pub.dev" 522 | source: hosted 523 | version: "5.1.0" 524 | stream_channel: 525 | dependency: transitive 526 | description: 527 | name: stream_channel 528 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 529 | url: "https://pub.dev" 530 | source: hosted 531 | version: "2.1.2" 532 | stream_transform: 533 | dependency: transitive 534 | description: 535 | name: stream_transform 536 | sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" 537 | url: "https://pub.dev" 538 | source: hosted 539 | version: "2.1.0" 540 | string_scanner: 541 | dependency: transitive 542 | description: 543 | name: string_scanner 544 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 545 | url: "https://pub.dev" 546 | source: hosted 547 | version: "1.2.0" 548 | term_glyph: 549 | dependency: transitive 550 | description: 551 | name: term_glyph 552 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 553 | url: "https://pub.dev" 554 | source: hosted 555 | version: "1.2.1" 556 | test_api: 557 | dependency: transitive 558 | description: 559 | name: test_api 560 | sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" 561 | url: "https://pub.dev" 562 | source: hosted 563 | version: "0.7.0" 564 | timing: 565 | dependency: transitive 566 | description: 567 | name: timing 568 | sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" 569 | url: "https://pub.dev" 570 | source: hosted 571 | version: "1.0.1" 572 | tuple: 573 | dependency: transitive 574 | description: 575 | name: tuple 576 | sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 577 | url: "https://pub.dev" 578 | source: hosted 579 | version: "2.0.2" 580 | typed_data: 581 | dependency: transitive 582 | description: 583 | name: typed_data 584 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 585 | url: "https://pub.dev" 586 | source: hosted 587 | version: "1.3.2" 588 | uuid: 589 | dependency: transitive 590 | description: 591 | name: uuid 592 | sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 593 | url: "https://pub.dev" 594 | source: hosted 595 | version: "4.3.3" 596 | watcher: 597 | dependency: transitive 598 | description: 599 | name: watcher 600 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 601 | url: "https://pub.dev" 602 | source: hosted 603 | version: "1.1.0" 604 | web: 605 | dependency: transitive 606 | description: 607 | name: web 608 | sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" 609 | url: "https://pub.dev" 610 | source: hosted 611 | version: "0.4.2" 612 | web_socket_channel: 613 | dependency: transitive 614 | description: 615 | name: web_socket_channel 616 | sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" 617 | url: "https://pub.dev" 618 | source: hosted 619 | version: "2.4.3" 620 | xml: 621 | dependency: transitive 622 | description: 623 | name: xml 624 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 625 | url: "https://pub.dev" 626 | source: hosted 627 | version: "6.5.0" 628 | yaml: 629 | dependency: transitive 630 | description: 631 | name: yaml 632 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 633 | url: "https://pub.dev" 634 | source: hosted 635 | version: "3.1.2" 636 | sdks: 637 | dart: ">=3.2.0 <4.0.0" 638 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: alfred_so 2 | description: Search for answers on StackOverflow from Alfred 3 | 4 | # Prevent accidental publishing to pub.dev. 5 | publish_to: 'none' 6 | 7 | version: 1.1.0 8 | 9 | environment: 10 | sdk: '>=3.2.0 <4.0.0' 11 | 12 | dependencies: 13 | args: ^2.4.2 14 | alfred_workflow: ^0.6.1 15 | autoequal: ^0.9.1 16 | cli_script: ^1.0.0 17 | crypto: ^3.0.3 18 | collection: ^1.18.0 19 | envied: ^0.5.3 20 | equatable: ^2.0.5 21 | html_unescape: ^2.0.0 22 | http: ^1.1.2 23 | json_annotation: ^4.8.1 24 | path: ^1.9.0 25 | stash: ^5.0.3 26 | 27 | dev_dependencies: 28 | autoequal_gen: ^0.9.1 29 | build_runner: ^2.4.7 30 | envied_generator: ^0.5.3 31 | json_serializable: ^6.7.1 32 | lints: ^3.0.0 33 | --------------------------------------------------------------------------------