Full Code of matryer/xbar for AI

main d62423905899 cached
708 files
4.6 MB
1.2M tokens
364 symbols
1 requests
Download .txt
Showing preview only (4,935K chars total). Download the full file or copy to clipboard to get everything.
Repository: matryer/xbar
Branch: main
Commit: d62423905899
Files: 708
Total size: 4.6 MB

Directory structure:
gitextract_oblxmil8/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── deploy-xbarappcom.yaml
├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE.txt
├── README.md
├── app/
│   ├── .gitignore
│   ├── README.md
│   ├── app.go
│   ├── build.sh
│   ├── categories_service.go
│   ├── categories_service_test.go
│   ├── command_service.go
│   ├── frontend/
│   │   ├── .gitignore
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── public/
│   │   │   └── index.html
│   │   ├── rollup.config.js
│   │   ├── src/
│   │   │   ├── App.svelte
│   │   │   ├── Homepage.svelte
│   │   │   ├── InstalledPluginView.svelte
│   │   │   ├── PersonView.svelte
│   │   │   ├── PluginView.svelte
│   │   │   ├── PluginsList.svelte
│   │   │   ├── backend/
│   │   │   │   ├── index.js
│   │   │   │   └── package.json
│   │   │   ├── elements/
│   │   │   │   ├── A.svelte
│   │   │   │   ├── Breadcrumbs.svelte
│   │   │   │   ├── Button.svelte
│   │   │   │   ├── Duration.svelte
│   │   │   │   ├── Error.svelte
│   │   │   │   ├── KeyboardShortcuts.svelte
│   │   │   │   ├── PluginCollection.svelte
│   │   │   │   ├── PluginDetails.svelte
│   │   │   │   ├── PluginSourceBrowser.svelte
│   │   │   │   ├── Spinner.svelte
│   │   │   │   ├── Switch.svelte
│   │   │   │   ├── VariableInput.svelte
│   │   │   │   └── Variables.svelte
│   │   │   ├── main.js
│   │   │   ├── pagedata.svelte
│   │   │   ├── rpc.svelte
│   │   │   ├── signals.svelte
│   │   │   ├── styles.css
│   │   │   └── waiters.svelte
│   │   └── tailwind.config.js
│   ├── go.mod
│   ├── go.sum
│   ├── incoming_urls.go
│   ├── incoming_urls_test.go
│   ├── main.go
│   ├── menu_parser.go
│   ├── menu_parser_test.go
│   ├── package.json
│   ├── package.sh
│   ├── person_service.go
│   ├── plugins_service.go
│   ├── settings.go
│   ├── settings_test.go
│   ├── test.sh
│   └── wails.json
├── archive/
│   └── bitbar/
│       ├── .gitignore
│       ├── .gitmodules
│       ├── .travis.yml
│       ├── App/
│       │   ├── BitBar/
│       │   │   ├── App.xib
│       │   │   ├── AppDelegate.m
│       │   │   ├── Base.lproj/
│       │   │   │   └── MainMenu.xib
│       │   │   ├── BitBar-Info.plist
│       │   │   ├── CHANGELOG.md
│       │   │   ├── ExecutablePlugin.h
│       │   │   ├── ExecutablePlugin.m
│       │   │   ├── HTMLPlugin.h
│       │   │   ├── HTMLPlugin.m
│       │   │   ├── Images.xcassets/
│       │   │   │   └── AppIcon.appiconset/
│       │   │   │       └── Contents.json
│       │   │   ├── NSColor+Hex.h
│       │   │   ├── NSColor+Hex.m
│       │   │   ├── NSString+ANSI.h
│       │   │   ├── NSString+ANSI.m
│       │   │   ├── NSUserDefaults+Settings.h
│       │   │   ├── NSUserDefaults+Settings.m
│       │   │   ├── Plugin.h
│       │   │   ├── Plugin.m
│       │   │   ├── PluginManager.h
│       │   │   ├── PluginManager.m
│       │   │   └── incoming-url-tests.html
│       │   ├── BitBar.xcodeproj/
│       │   │   ├── project.pbxproj
│       │   │   ├── project.xcworkspace/
│       │   │   │   ├── contents.xcworkspacedata
│       │   │   │   └── xcshareddata/
│       │   │   │       ├── BitBar.xccheckout
│       │   │   │       ├── BitBar.xcscmblueprint
│       │   │   │       └── IDEWorkspaceChecks.plist
│       │   │   └── xcshareddata/
│       │   │       └── xcschemes/
│       │   │           ├── BitBar.xcscheme
│       │   │           └── BitBarDistro.xcscheme
│       │   ├── BitBarTests/
│       │   │   ├── BitBarTests-Info.plist
│       │   │   ├── PluginManager+Test.h
│       │   │   ├── PluginManager+Test.m
│       │   │   ├── PluginManagerTest.m
│       │   │   ├── PluginTest.m
│       │   │   └── TestPlugins/
│       │   │       ├── one.10s.sh
│       │   │       ├── three.7d.sh
│       │   │       └── two.5m.sh
│       │   └── Vendor/
│       │       ├── AHProxySettings/
│       │       │   ├── .gitignore
│       │       │   ├── AHProxyExampe/
│       │       │   │   ├── AppDelegate.h
│       │       │   │   ├── AppDelegate.m
│       │       │   │   ├── Base.lproj/
│       │       │   │   │   └── MainMenu.xib
│       │       │   │   ├── Images.xcassets/
│       │       │   │   │   └── AppIcon.appiconset/
│       │       │   │   │       └── Contents.json
│       │       │   │   ├── Info.plist
│       │       │   │   └── main.m
│       │       │   ├── AHProxySettings/
│       │       │   │   ├── AHProxy.h
│       │       │   │   ├── AHProxy.m
│       │       │   │   ├── AHProxySettings.h
│       │       │   │   ├── AHProxySettings.m
│       │       │   │   ├── NSTask+useSystemProxies.h
│       │       │   │   └── NSTask+useSystemProxies.m
│       │       │   ├── AHProxySettings.podspec
│       │       │   ├── AHProxySettings.xcodeproj/
│       │       │   │   ├── project.pbxproj
│       │       │   │   ├── project.xcworkspace/
│       │       │   │   │   └── contents.xcworkspacedata
│       │       │   │   └── xcshareddata/
│       │       │   │       └── xcschemes/
│       │       │   │           └── AHProxySettings.xcscheme
│       │       │   ├── AHProxySettingsTests/
│       │       │   │   ├── AHProxySettingsTest.m
│       │       │   │   └── Info.plist
│       │       │   ├── LICENSE
│       │       │   └── README.md
│       │       ├── DateTools/
│       │       │   ├── .gitignore
│       │       │   ├── .travis.yml
│       │       │   ├── CREDITS.md
│       │       │   ├── DateTools/
│       │       │   │   ├── DTConstants.h
│       │       │   │   ├── DTConstants.m
│       │       │   │   ├── DTError.h
│       │       │   │   ├── DTError.m
│       │       │   │   ├── DTTimePeriod.h
│       │       │   │   ├── DTTimePeriod.m
│       │       │   │   ├── DTTimePeriodChain.h
│       │       │   │   ├── DTTimePeriodChain.m
│       │       │   │   ├── DTTimePeriodCollection.h
│       │       │   │   ├── DTTimePeriodCollection.m
│       │       │   │   ├── DTTimePeriodGroup.h
│       │       │   │   ├── DTTimePeriodGroup.m
│       │       │   │   ├── DateTools.bundle/
│       │       │   │   │   ├── ar.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── bg.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ca.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── cs.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── cy.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── da.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── de.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── en.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── es.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── eu.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── fi.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── fr.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── gre.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── gu.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── he.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── hi.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── hr.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── hu.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── id.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── is.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── it.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ja.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ko.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── lv.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ms.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── nb.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── nl.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── pl.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── pt-PT.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── pt.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ro.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ru.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── sl.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── sv.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── th.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── tr.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── uk.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── vi.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── zh-Hans.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   └── zh-Hant.lproj/
│       │       │   │   │       └── DateTools.strings
│       │       │   │   ├── DateTools.h
│       │       │   │   ├── NSDate+DateTools.h
│       │       │   │   └── NSDate+DateTools.m
│       │       │   ├── DateTools.podspec
│       │       │   ├── Examples/
│       │       │   │   └── DateToolsExample/
│       │       │   │       ├── DateTools/
│       │       │   │       │   └── Info.plist
│       │       │   │       ├── DateToolsExample/
│       │       │   │       │   ├── AppDelegate.h
│       │       │   │       │   ├── AppDelegate.m
│       │       │   │       │   ├── Colours.h
│       │       │   │       │   ├── Colours.m
│       │       │   │       │   ├── DateToolsExample-Info.plist
│       │       │   │       │   ├── DateToolsExample-Prefix.pch
│       │       │   │       │   ├── DateToolsViewController.h
│       │       │   │       │   ├── DateToolsViewController.m
│       │       │   │       │   ├── DateToolsViewController.xib
│       │       │   │       │   ├── ExampleNavigationController.h
│       │       │   │       │   ├── ExampleNavigationController.m
│       │       │   │       │   ├── Images.xcassets/
│       │       │   │       │   │   ├── AppIcon.appiconset/
│       │       │   │       │   │   │   └── Contents.json
│       │       │   │       │   │   └── LaunchImage.launchimage/
│       │       │   │       │   │       └── Contents.json
│       │       │   │       │   ├── TimePeriodsViewController.h
│       │       │   │       │   ├── TimePeriodsViewController.m
│       │       │   │       │   ├── TimePeriodsViewController.xib
│       │       │   │       │   ├── en.lproj/
│       │       │   │       │   │   └── InfoPlist.strings
│       │       │   │       │   └── main.m
│       │       │   │       ├── DateToolsExample.xcodeproj/
│       │       │   │       │   ├── project.pbxproj
│       │       │   │       │   ├── project.xcworkspace/
│       │       │   │       │   │   └── contents.xcworkspacedata
│       │       │   │       │   └── xcshareddata/
│       │       │   │       │       └── xcschemes/
│       │       │   │       │           └── DateTools.xcscheme
│       │       │   │       └── DateToolsExampleTests/
│       │       │   │           ├── DateToolsExampleTests-Info.plist
│       │       │   │           └── en.lproj/
│       │       │   │               └── InfoPlist.strings
│       │       │   ├── LICENSE
│       │       │   ├── README.md
│       │       │   └── Tests/
│       │       │       └── DateToolsTests/
│       │       │           ├── DateToolsTests/
│       │       │           │   ├── AppDelegate.h
│       │       │           │   ├── AppDelegate.m
│       │       │           │   ├── Base.lproj/
│       │       │           │   │   └── Main.storyboard
│       │       │           │   ├── DateToolsTests-Info.plist
│       │       │           │   ├── DateToolsTests-Prefix.pch
│       │       │           │   ├── Images.xcassets/
│       │       │           │   │   ├── AppIcon.appiconset/
│       │       │           │   │   │   └── Contents.json
│       │       │           │   │   └── LaunchImage.launchimage/
│       │       │           │   │       └── Contents.json
│       │       │           │   ├── ViewController.h
│       │       │           │   ├── ViewController.m
│       │       │           │   ├── en.lproj/
│       │       │           │   │   └── InfoPlist.strings
│       │       │           │   ├── es.lproj/
│       │       │           │   │   ├── InfoPlist.strings
│       │       │           │   │   └── Main.strings
│       │       │           │   ├── ja.lproj/
│       │       │           │   │   ├── InfoPlist.strings
│       │       │           │   │   └── Main.strings
│       │       │           │   └── main.m
│       │       │           ├── DateToolsTests.xcodeproj/
│       │       │           │   ├── project.pbxproj
│       │       │           │   ├── project.xcworkspace/
│       │       │           │   │   └── contents.xcworkspacedata
│       │       │           │   └── xcshareddata/
│       │       │           │       └── xcschemes/
│       │       │           │           ├── DateToolsTests.xcscheme
│       │       │           │           └── DateToolsTestsTests.xcscheme
│       │       │           └── DateToolsTestsTests/
│       │       │               ├── DTTimeAgoTests.m
│       │       │               ├── DTTimePeriodChainTests.m
│       │       │               ├── DTTimePeriodCollectionTests.m
│       │       │               ├── DTTimePeriodGroupTests.m
│       │       │               ├── DTTimePeriodTests.m
│       │       │               ├── DateToolsTests.m
│       │       │               ├── DateToolsTestsTests-Info.plist
│       │       │               ├── en.lproj/
│       │       │               │   └── InfoPlist.strings
│       │       │               ├── es.lproj/
│       │       │               │   └── InfoPlist.strings
│       │       │               └── ja.lproj/
│       │       │                   └── InfoPlist.strings
│       │       ├── LaunchAtLoginController/
│       │       │   ├── LaunchAtLoginController.h
│       │       │   ├── LaunchAtLoginController.m
│       │       │   └── README.md
│       │       ├── NSStringEmojize/
│       │       │   ├── .gitignore
│       │       │   ├── Example/
│       │       │   │   ├── NSStringEmojize/
│       │       │   │   │   ├── DIYAppDelegate.h
│       │       │   │   │   ├── DIYAppDelegate.m
│       │       │   │   │   ├── DIYViewController.h
│       │       │   │   │   ├── DIYViewController.m
│       │       │   │   │   ├── NSStringEmojize-Info.plist
│       │       │   │   │   ├── NSStringEmojize-Prefix.pch
│       │       │   │   │   ├── en.lproj/
│       │       │   │   │   │   └── InfoPlist.strings
│       │       │   │   │   └── main.m
│       │       │   │   ├── NSStringEmojize.xcodeproj/
│       │       │   │   │   ├── project.pbxproj
│       │       │   │   │   └── project.xcworkspace/
│       │       │   │   │       └── contents.xcworkspacedata
│       │       │   │   └── NSStringEmojizeTests/
│       │       │   │       ├── NSStringEmojizeTests-Info.plist
│       │       │   │       ├── NSStringEmojizeTests.h
│       │       │   │       ├── NSStringEmojizeTests.m
│       │       │   │       └── en.lproj/
│       │       │   │           └── InfoPlist.strings
│       │       │   ├── LICENSE.md
│       │       │   ├── NSStringEmojize/
│       │       │   │   ├── NSString+Emojize.h
│       │       │   │   ├── NSString+Emojize.m
│       │       │   │   └── emojis.h
│       │       │   ├── NSStringEmojize.podspec
│       │       │   └── README.md
│       │       ├── STPrivilegedTask/
│       │       │   ├── .gitignore
│       │       │   ├── LICENSE.txt
│       │       │   ├── PrivilegedTaskExample/
│       │       │   │   ├── PrivilegedTaskExample/
│       │       │   │   │   ├── AppDelegate.h
│       │       │   │   │   ├── AppDelegate.m
│       │       │   │   │   ├── Base.lproj/
│       │       │   │   │   │   └── MainMenu.xib
│       │       │   │   │   ├── Info.plist
│       │       │   │   │   ├── lock-icon.icns
│       │       │   │   │   └── main.m
│       │       │   │   └── PrivilegedTaskExample.xcodeproj/
│       │       │   │       └── project.pbxproj
│       │       │   ├── README.md
│       │       │   ├── STPrivilegedTask.h
│       │       │   ├── STPrivilegedTask.m
│       │       │   └── STPrivilegedTask.podspec
│       │       └── Sparkle/
│       │           ├── .clang-format
│       │           ├── .gitignore
│       │           ├── .travis.yml
│       │           ├── CHANGELOG
│       │           ├── Configurations/
│       │           │   ├── ConfigBinaryDelta.xcconfig
│       │           │   ├── ConfigBinaryDeltaDebug.xcconfig
│       │           │   ├── ConfigBinaryDeltaRelease.xcconfig
│       │           │   ├── ConfigCommon.xcconfig
│       │           │   ├── ConfigCommonCoverage.xcconfig
│       │           │   ├── ConfigCommonDebug.xcconfig
│       │           │   ├── ConfigCommonRelease.xcconfig
│       │           │   ├── ConfigFileop.xcconfig
│       │           │   ├── ConfigFramework.xcconfig
│       │           │   ├── ConfigFrameworkDebug.xcconfig
│       │           │   ├── ConfigFrameworkRelease.xcconfig
│       │           │   ├── ConfigRelaunch.xcconfig
│       │           │   ├── ConfigRelaunchDebug.xcconfig
│       │           │   ├── ConfigRelaunchRelease.xcconfig
│       │           │   ├── ConfigTestApp.xcconfig
│       │           │   ├── ConfigTestAppDebug.xcconfig
│       │           │   ├── ConfigTestAppRelease.xcconfig
│       │           │   ├── ConfigUITest.xcconfig
│       │           │   ├── ConfigUITestCoverage.xcconfig
│       │           │   ├── ConfigUITestDebug.xcconfig
│       │           │   ├── ConfigUITestRelease.xcconfig
│       │           │   ├── ConfigUnitTest.xcconfig
│       │           │   ├── ConfigUnitTestCoverage.xcconfig
│       │           │   ├── ConfigUnitTestDebug.xcconfig
│       │           │   ├── ConfigUnitTestRelease.xcconfig
│       │           │   ├── make-release-package.sh
│       │           │   └── set-git-version-info.sh
│       │           ├── Documentation/
│       │           │   ├── .gitignore
│       │           │   ├── Doxyfile
│       │           │   └── build-docs.sh
│       │           ├── LICENSE
│       │           ├── Makefile
│       │           ├── README.markdown
│       │           ├── Resources/
│       │           │   ├── Images.xcassets/
│       │           │   │   └── AppIcon.appiconset/
│       │           │   │       └── Contents.json
│       │           │   ├── SUModelTranslation.plist
│       │           │   ├── SampleAppcast.xml
│       │           │   └── Sparkle-Icon.sketch
│       │           ├── Sparkle/
│       │           │   ├── Autoupdate/
│       │           │   │   ├── Autoupdate-Info.plist
│       │           │   │   └── Autoupdate.m
│       │           │   ├── CheckLocalizations.swift
│       │           │   ├── SUAppcast.h
│       │           │   ├── SUAppcast.m
│       │           │   ├── SUAppcastItem.h
│       │           │   ├── SUAppcastItem.m
│       │           │   ├── SUAutomaticUpdateAlert.h
│       │           │   ├── SUAutomaticUpdateAlert.m
│       │           │   ├── SUAutomaticUpdateDriver.h
│       │           │   ├── SUAutomaticUpdateDriver.m
│       │           │   ├── SUBasicUpdateDriver.h
│       │           │   ├── SUBasicUpdateDriver.m
│       │           │   ├── SUBinaryDeltaApply.h
│       │           │   ├── SUBinaryDeltaApply.m
│       │           │   ├── SUBinaryDeltaCommon.h
│       │           │   ├── SUBinaryDeltaCommon.m
│       │           │   ├── SUBinaryDeltaCreate.h
│       │           │   ├── SUBinaryDeltaCreate.m
│       │           │   ├── SUBinaryDeltaTool.m
│       │           │   ├── SUBinaryDeltaUnarchiver.h
│       │           │   ├── SUBinaryDeltaUnarchiver.m
│       │           │   ├── SUCodeSigningVerifier.h
│       │           │   ├── SUCodeSigningVerifier.m
│       │           │   ├── SUConstants.h
│       │           │   ├── SUConstants.m
│       │           │   ├── SUDSAVerifier.h
│       │           │   ├── SUDSAVerifier.m
│       │           │   ├── SUDiskImageUnarchiver.h
│       │           │   ├── SUDiskImageUnarchiver.m
│       │           │   ├── SUErrors.h
│       │           │   ├── SUExport.h
│       │           │   ├── SUFileManager.h
│       │           │   ├── SUFileManager.m
│       │           │   ├── SUGuidedPackageInstaller.h
│       │           │   ├── SUGuidedPackageInstaller.m
│       │           │   ├── SUHost.h
│       │           │   ├── SUHost.m
│       │           │   ├── SUInstaller.h
│       │           │   ├── SUInstaller.m
│       │           │   ├── SULog.h
│       │           │   ├── SULog.m
│       │           │   ├── SUOperatingSystem.h
│       │           │   ├── SUOperatingSystem.m
│       │           │   ├── SUPackageInstaller.h
│       │           │   ├── SUPackageInstaller.m
│       │           │   ├── SUPipedUnarchiver.h
│       │           │   ├── SUPipedUnarchiver.m
│       │           │   ├── SUPlainInstaller.h
│       │           │   ├── SUPlainInstaller.m
│       │           │   ├── SUProbingUpdateDriver.h
│       │           │   ├── SUProbingUpdateDriver.m
│       │           │   ├── SUScheduledUpdateDriver.h
│       │           │   ├── SUScheduledUpdateDriver.m
│       │           │   ├── SUStandardVersionComparator.h
│       │           │   ├── SUStandardVersionComparator.m
│       │           │   ├── SUStatus.xib
│       │           │   ├── SUStatusController.h
│       │           │   ├── SUStatusController.m
│       │           │   ├── SUSystemProfiler.h
│       │           │   ├── SUSystemProfiler.m
│       │           │   ├── SUUIBasedUpdateDriver.h
│       │           │   ├── SUUIBasedUpdateDriver.m
│       │           │   ├── SUUnarchiver.h
│       │           │   ├── SUUnarchiver.m
│       │           │   ├── SUUnarchiver_Private.h
│       │           │   ├── SUUpdateAlert.h
│       │           │   ├── SUUpdateAlert.m
│       │           │   ├── SUUpdateDriver.h
│       │           │   ├── SUUpdateDriver.m
│       │           │   ├── SUUpdatePermissionPrompt.h
│       │           │   ├── SUUpdatePermissionPrompt.m
│       │           │   ├── SUUpdater.h
│       │           │   ├── SUUpdater.m
│       │           │   ├── SUUpdater_Private.h
│       │           │   ├── SUUserInitiatedUpdateDriver.h
│       │           │   ├── SUUserInitiatedUpdateDriver.m
│       │           │   ├── SUVersionComparisonProtocol.h
│       │           │   ├── SUVersionDisplayProtocol.h
│       │           │   ├── SUWindowController.h
│       │           │   ├── SUWindowController.m
│       │           │   ├── Sparkle-Info.plist
│       │           │   ├── Sparkle.h
│       │           │   ├── Sparkle.pch
│       │           │   ├── ar.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ca.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── cs.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── da.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── de.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── el.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── en.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── es.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── fi.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── fr.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── he.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── is.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── it.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ja.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ko.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── nb.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── nl.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── no.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── pl.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── pt_BR.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── pt_PT.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ro.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ru.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── sk.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── sl.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── sv.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── th.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── tr.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── uk.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── zh_CN.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   └── zh_TW.lproj/
│       │           │       ├── SUAutomaticUpdateAlert.xib
│       │           │       ├── SUUpdateAlert.xib
│       │           │       ├── SUUpdatePermissionPrompt.xib
│       │           │       └── Sparkle.strings
│       │           ├── Sparkle.podspec
│       │           ├── Sparkle.xcodeproj/
│       │           │   ├── project.pbxproj
│       │           │   ├── project.xcworkspace/
│       │           │   │   └── contents.xcworkspacedata
│       │           │   └── xcshareddata/
│       │           │       └── xcschemes/
│       │           │           ├── Distribution.xcscheme
│       │           │           ├── Sparkle.xcscheme
│       │           │           └── UITests.xcscheme
│       │           ├── TestApplication/
│       │           │   ├── English.lproj/
│       │           │   │   ├── InfoPlist.strings
│       │           │   │   └── MainMenu.xib
│       │           │   ├── SUTestApplicationDelegate.h
│       │           │   ├── SUTestApplicationDelegate.m
│       │           │   ├── SUTestWebServer.h
│       │           │   ├── SUTestWebServer.m
│       │           │   ├── SUUpdateSettingsWindowController.h
│       │           │   ├── SUUpdateSettingsWindowController.m
│       │           │   ├── SUUpdateSettingsWindowController.xib
│       │           │   ├── TestApplication-Info.plist
│       │           │   ├── ar.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ca.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── cs.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── cy.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── da.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── de.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── el.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── es.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── fi.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── fr-CA.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── fr.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── he.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── hu.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── id.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── is.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── it.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ja.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ko.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── main.m
│       │           │   ├── nb.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── nl.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pl.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pt-BR.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pt-PT.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pt.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ro.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ru.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── sk.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── sl.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── sparkletestcast.xml
│       │           │   ├── sv.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── test_app_only_dsa_priv_dont_ever_do_this_for_real.pem
│       │           │   ├── test_app_only_dsa_pub.pem
│       │           │   ├── th.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── tr.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── uk.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── zh-Hans.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   └── zh-Hant.lproj/
│       │           │       └── InfoPlist.strings
│       │           ├── Tests/
│       │           │   ├── Resources/
│       │           │   │   ├── SparkleTestCodeSignApp.dmg
│       │           │   │   ├── SparkleTestCodeSignApp.enc.dmg
│       │           │   │   ├── SparkleTestCodeSignApp.tar.bz2
│       │           │   │   ├── SparkleTestCodeSignApp.tar.xz
│       │           │   │   ├── signed-test-file.txt
│       │           │   │   ├── test-pubkey.pem
│       │           │   │   ├── test.sparkle_guided.pkg
│       │           │   │   ├── testappcast.xml
│       │           │   │   └── testnamespaces.xml
│       │           │   ├── SUAppcastTest.swift
│       │           │   ├── SUBinaryDeltaTest.m
│       │           │   ├── SUCodeSigningVerifierTest.m
│       │           │   ├── SUDSAVerifierTest.m
│       │           │   ├── SUFileManagerTest.swift
│       │           │   ├── SUInstallerTest.m
│       │           │   ├── SUUnarchiverTest.swift
│       │           │   ├── SUUpdaterTest.m
│       │           │   ├── SUVersionComparisonTest.m
│       │           │   ├── Sparkle Unit Tests-Bridging-Header.h
│       │           │   └── SparkleTests-Info.plist
│       │           ├── UITests/
│       │           │   ├── SUTestApplicationTest.swift
│       │           │   └── UITests-Info.plist
│       │           ├── Vendor/
│       │           │   ├── CocoatechCore/
│       │           │   │   ├── NTSynchronousTask.h
│       │           │   │   └── NTSynchronousTask.m
│       │           │   └── bsdiff/
│       │           │       ├── bscommon.c
│       │           │       ├── bscommon.h
│       │           │       ├── bsdiff.c
│       │           │       ├── bspatch.c
│       │           │       ├── bspatch.h
│       │           │       ├── sais.c
│       │           │       └── sais.h
│       │           ├── bin/
│       │           │   ├── generate_keys
│       │           │   └── sign_update
│       │           └── fileop/
│       │               ├── SUFileOperationConstants.h
│       │               ├── SUFileOperationConstants.m
│       │               └── fileop.m
│       ├── Docs/
│       │   ├── DistributingBitBar.md
│       │   └── URLScheme.md
│       ├── Makefile
│       ├── README.md
│       └── Scripts/
│           └── bitbar-bundler
├── pkg/
│   ├── metadata/
│   │   ├── categories_metadata.go
│   │   ├── categories_metadata_test.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── plugin_metadata.go
│   │   └── plugin_metadata_test.go
│   ├── plugins/
│   │   ├── README.md
│   │   ├── action.go
│   │   ├── action_test.go
│   │   ├── colors.go
│   │   ├── emoji.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── install.go
│   │   ├── install_test.go
│   │   ├── installed_plugins.go
│   │   ├── installed_plugins_test.go
│   │   ├── item_params.go
│   │   ├── item_params_test.go
│   │   ├── parse.go
│   │   ├── parse_test.go
│   │   ├── plugin.go
│   │   ├── plugin_darwin.go
│   │   ├── plugin_linux.go
│   │   ├── plugin_test.go
│   │   ├── plugin_windows.go
│   │   ├── refresh_interval.go
│   │   ├── refresh_interval_test.go
│   │   ├── testdata/
│   │   │   ├── broken-plugins/
│   │   │   │   ├── broken.1m.sh
│   │   │   │   └── wont-quit.1m.sh
│   │   │   ├── plugins/
│   │   │   │   ├── 001-multiple.1s.sh
│   │   │   │   ├── 001-multiple.1s.sh.vars.json
│   │   │   │   ├── 002-multiple.1s.sh
│   │   │   │   ├── dirs-should-be-ignored/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── expanded.1m.sh
│   │   │   │   ├── expanded.1m.sh.off
│   │   │   │   ├── expanded.1s.sh
│   │   │   │   ├── params.3s.sh
│   │   │   │   ├── simple.1m.sh
│   │   │   │   ├── simple.1m.sh.disabled
│   │   │   │   └── simple.1s.sh
│   │   │   ├── stub-api/
│   │   │   │   └── currency-tracker.1h.py.json
│   │   │   ├── token-too-long/
│   │   │   │   ├── jma.1h.sh
│   │   │   │   └── jma.1h.sh.output
│   │   │   └── vars-test/
│   │   │       ├── plugin.sh
│   │   │       └── plugin.sh.vars.json
│   │   ├── variables.go
│   │   └── variables_test.go
│   └── update/
│       ├── README.md
│       ├── go.mod
│       ├── go.sum
│       ├── update.go
│       ├── update_test.go
│       └── updatetest/
│           └── main.go
├── talk-overview.md
├── tools/
│   ├── sitegen/
│   │   ├── README.md
│   │   ├── docs.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── images.go
│   │   ├── main.go
│   │   ├── repo.go
│   │   ├── repo_test.go
│   │   └── run.sh
│   └── xbarmdcheck/
│       ├── README.md
│       ├── go.mod
│       ├── go.sum
│       ├── main.go
│       └── testdata/
│           └── sample-plugin.sh
└── xbarapp.com/
    ├── .gcloudignore
    ├── README.md
    ├── app.yaml
    ├── articles/
    │   └── 2021/
    │       └── 03/
    │           ├── 13/
    │           │   └── Upgrade-legacy-BitBar-plugins.md
    │           └── 14/
    │               └── Variables-in-xbar.md
    ├── build.sh
    ├── deploy.sh
    ├── download.go
    ├── gen.sh
    ├── go.mod
    ├── go.sum
    ├── main.go
    ├── main_test.go
    ├── package.json
    ├── postcss.config.js
    ├── public/
    │   └── css/
    │       └── xbar.css
    ├── run.sh
    ├── styles.css
    ├── tailwind.config.js
    └── templates/
        ├── _layout.html
        ├── article.html
        ├── articles-index.html
        ├── category.html
        ├── contributor.html
        ├── contributors.html
        ├── index.html
        └── plugin.html

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: matryer


================================================
FILE: .github/workflows/deploy-xbarappcom.yaml
================================================
name: Build and Deploy

on:
  push:
    branches:
      - main
  schedule:
    - cron: "0 0 * * 0" # every week

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          fetch-depth: 0 # Fetch all history for all tags and branches

      - name: Set up Go
        uses: actions/setup-go@v3
        with:
          go-version: "1.22"

      - name: Run sitegen
        working-directory: tools/sitegen
        env:
          XBAR_GITHUB_ACCESS_TOKEN: ${{ secrets.XBAR_GITHUB_ACCESS_TOKEN }}
        run: ./run.sh -skipdata

      - name: Auth with Google Cloud
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_CREDENTIALS }}

      - name: Set up Cloud SDK
        uses: google-github-actions/setup-gcloud@v2
        with:
          version: ">= 363.0.0"

      - name: Deploy to Google App Engine
        working-directory: xbarapp.com
        env:
          GCP_CREDENTIALS: ${{ secrets.GCP_CREDENTIALS }}
        run: |
          VERSION=$(date +%Y%m%d%H%M%S)
          gcloud app deploy --project xbarapp --version $VERSION --quiet --verbosity=debug


================================================
FILE: .gitignore
================================================
.DS_Store
.idea
app/frontend/package.json.md5
xbarapp.com/public/docs/
tools/sitegen/sitegen
app/node_modules
app/build/bin
pkg/update/testarea
xbarapp.com/node_modules
xbarapp.com/public/docs
xbarapp.com/package-lock.json
app/build/darwin/info.plist
app/frontend/package-lock.json
app/build/.DS_Store
app/.DS_Store
xbarapp.com/public/.DS_Store
tools/sitegen/.version
app/.version
xbarapp.com/.version
xbarapp.com/xbarappcom
xbarapp.com/version.gen.go
tools/bloggen/.version
pkg/.DS_Store


================================================
FILE: .vscode/settings.json
================================================
{
    "files.exclude": {
        "archive": true
    }
}

================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) 2014-2023 Mat Ryer + contributors

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: README.md
================================================
[![](xbarapp.com/public/img/xbar-menu-preview.png)](https://xbarapp.com/)

# Welcome to xbar

xbar (the BitBar reboot) lets you put the output from any script/program in your macOS menu bar.

  * **Complete rewrite from the ground up** - in Go by @matryer and @leaanthony - using [Wails.app (build cross-platform desktop apps using Go & HTML/CSS/JS)](https://wails.app)
  * Completely open source
  * [Download latest xbar release](https://github.com/matryer/xbar/releases/latest) - requires macOS Catalina or newer (>= 10.15)
  * [Visit the app homepage at https://xbarapp.com](https://xbarapp.com)
  * [Get started](#get-started) and [installing plugins](#installing-plugins)

Digging deeper:

  * [Browse plugin repository](https://xbarapp.com/)
  * [Guide to writing your own plugins](https://github.com/matryer/xbar-plugins/blob/main/CONTRIBUTING.md)

And finally...

  * [Read the story about how xbar unexpectedly got going](https://medium.com/@matryer/what-happens-when-your-old-open-source-project-unexpectedly-gets-to-the-top-of-hacker-news-31114c6c6efb#.fznvtgskb)
  * [Contributing](#contributing) and [special thanks](#thanks)

## Get started

### Install

* [Download the latest release of xbar](https://github.com/matryer/xbar/releases).

## Installing plugins

From an xbar menu, choose **Preferences > Plugins...** to use the xbar app to discover and manage plugins.

You can [browse all the plugins](https://xbarapp.com/) online, or [write your own](https://github.com/matryer/xbar-plugins/blob/main/CONTRIBUTING.md).

### The Plugin Directory

The plugin directory is folder on your Mac where the plugins live, located at `~/Library/Application Support/xbar/plugins`.

* If you're transitioning from Bitbar, move your plugins into this new folder to install them

## Contributing

If you'd like to contribute a plugin, head over to https://github.com/matryer/xbar-plugins to get started.

Please do not send pull requests to this repo. Open an issue and start a conversation first. PRs will likely not be accepted.

* Get started with our [Writing plugins guide](https://github.com/matryer/xbar-plugins/blob/main/CONTRIBUTING.md)

## Thanks

  * Special thanks to [@leaanthony at https://wails.app](https://wails.app) and [@ianfoo](https://github.com/ianfoo), [@gingerbeardman](https://github.com/gingerbeardman), [@iosdeveloper](https://github.com/iosdeveloper), [@muhqu](https://github.com/muhqu), [@m-cat](https://github.com/m-cat), [@mpicard](https://github.com/mpicard), [@tylerb](https://github.com/tylerb) for their help
  * Thanks to [Chris Ryer](http://www.chrisryer.co.uk/) for the app logo - and to [@mazondo](https://twitter.com/mazondo) for the original
  * Thanks for all our [plugin contributors](https://xbarapp.com/) who have come up with some pretty genius things


================================================
FILE: app/.gitignore
================================================
app/frontend/package.json.md5
app/build
.idea

================================================
FILE: app/README.md
================================================
# xbar

Put anything into your macOS menu bar (sequel to BitBar)
 
## Development

To build xbar, you will need:

  * Go v1.15+
  * npm v6.14.9
  * Wails v2 cli - `go install github.com/wailsapp/wails/v2/cmd/wails@v2.0.0-alpha.72`

### Running

If running for the first time first generate the `.version` file and install the npm dependencies by running: 

```bash
cd app && git describe --tags > .version 
```

and 

```bash
cd app/frontend && npm install
```

To start the application run:

```bash
cd app/frontend && npm run dev
```

and

```bash
cd app && wails dev
```

### Building

In this directory run `./build.sh`. The binary will be generated in `./build/bin/`.

```bash
./build.sh && ./build/bin/xbar
```

### Updates

To use the latest version of the Wails library, ensure that go.mod is using the latest release tag.
The Wails CLI may be updated by running `wails update -pre`. When v2 is released, then `wails update` will keep you 
on the stable channel.

### Packaging

Tag the branch:

```bash
git tag -a v0.1.0 -m "release tag."
git push origin v0.1.0
```

```bash
./package.sh
```

* For code signing, xbar uses https://github.com/matryer/gon (fork of https://github.com/mitchellh/gon) 


================================================
FILE: app/app.go
================================================
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/pkg/errors"
	"github.com/wailsapp/wails/v2/pkg/mac"

	"github.com/gregjones/httpcache"
	"github.com/gregjones/httpcache/diskcache"
	"github.com/wailsapp/wails/v2/pkg/options/dialog"

	"github.com/matryer/xbar/pkg/plugins"
	"github.com/matryer/xbar/pkg/update"
	wails "github.com/wailsapp/wails/v2"
	"github.com/wailsapp/wails/v2/pkg/menu"
	"github.com/wailsapp/wails/v2/pkg/menu/keys"
)

var (
	pluginDirectory = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "xbar", "plugins")
	cacheDirectory  = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "xbar", "cache")
	configFilename  = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "xbar", "xbar.config.json")

	// concurrentIncomingURLs is the number of concurrent incoming URLs to handle at
	// the same time.
	concurrentIncomingURLs int = 1

	// apiRequestTimeout is the timeout for making API calls.
	apiRequestTimeout = 30 * time.Second
)

type app struct {
	runtime *wails.Runtime

	settings *settings

	// appMenu isn't visible - it's used for key shortcuts.
	appMenu           *menu.Menu
	contextMenus      []*menu.ContextMenu
	defaultTrayMenu   *menu.TrayMenu
	plugins           plugins.Plugins
	pluginTrays       map[string]*menu.TrayMenu
	menuParser        *MenuParser
	startsAtLoginMenu *menu.MenuItem
	autoupdateMenu    *menu.MenuItem
	appUpdatesMenu    *menu.MenuItem

	// Verbose gets whether verbose output will be printed
	// or not.
	Verbose bool

	CategoriesService *CategoriesService
	PluginsService    *PluginsService
	PersonService     *PersonService
	CommandService    *CommandService

	// incomingURLSemaphore is a buffered channel that keeps the
	// number of incoming URLs being parsed to one at a time.
	incomingURLSemaphore chan struct{}

	// lock protects menu items when RefreshAll
	// is called.
	// Also protects stopPluginsFunc, pluginsStoppedSignal,
	// menuIsOpen and isDarkMode.
	lock            sync.Mutex
	stopPluginsFunc context.CancelFunc
	// menuIsOpen keeps track of whether menus are open or not.
	// If they're open, they will not be updated.
	menuIsOpen bool
	// pluginsStoppedSignal is closed when plugins have stopped running.
	pluginsStoppedSignal  chan struct{}
	defaultTrayMenuActive bool
	// isDarkMode indicates whether the system is running
	// in dark mode or not.
	isDarkMode bool
}

// newApp makes a new app.
func newApp() (*app, error) {
	settings, err := loadSettings(configFilename)
	if err != nil {
		return nil, errors.Wrap(err, "loadSettings")
	}
	app := &app{
		settings:             settings,
		Verbose:              true,
		menuParser:           NewMenuParser(),
		incomingURLSemaphore: make(chan struct{}, concurrentIncomingURLs),
	}
	app.appMenu = menu.NewMenuFromItems(
		menu.AppMenu(),
		menu.EditMenu(),
		menu.WindowMenu(),
		&menu.MenuItem{
			Type:  menu.SubmenuType,
			Label: "Browser",
			SubMenu: menu.NewMenuFromItems(
				menu.Text("Refresh", keys.CmdOrCtrl("r"), app.onBrowserRefreshMenuClicked),
				menu.Text("Clear cache and refresh", keys.Combo("r", keys.CmdOrCtrlKey, keys.ShiftKey), app.onBrowserHardRefreshMenuClicked),
			),
		},
	)
	app.contextMenus = []*menu.ContextMenu{
		menu.NewContextMenu("refreshContextMenu", menu.NewMenuFromItems(
			menu.Text("Refresh page data", nil, app.onBrowserHardRefreshMenuClicked),
			menu.Text("Refresh plugins", nil, app.onPluginsRefreshAllMenuClicked),
			menu.Text("Clear Cache", nil, app.onClearCacheMenuClicked),
		)),
	}

	app.appUpdatesMenu = &menu.MenuItem{
		Type:  menu.TextType,
		Label: "Check for Updates…",
		Click: app.onCheckForUpdatesMenuClick,
	}

	app.autoupdateMenu = &menu.MenuItem{
		Label:   "Update Automatically",
		Type:    menu.CheckboxType,
		Checked: app.settings.AutoUpdate,
		Click:   app.updateAutoupdate,
	}

	app.startsAtLoginMenu = &menu.MenuItem{
		Label:   "Open at Login",
		Type:    menu.CheckboxType,
		Checked: false,
		Click:   app.updateStartOnLogin,
	}
	startsAtLogin, err := mac.StartsAtLogin()
	if err != nil {
		if app.Verbose {
			log.Println("open at login:", err)
		}
		app.startsAtLoginMenu.Label = "Open at Login"
		app.startsAtLoginMenu.Disabled = true
	} else {
		app.startsAtLoginMenu.Checked = startsAtLogin
	}

	// client-side caching to cacheDirectory
	tp := httpcache.NewTransport(diskcache.New(cacheDirectory))
	client := &http.Client{
		Transport: tp,
		Timeout:   apiRequestTimeout,
	}
	app.CategoriesService = NewCategoriesService(client)
	app.PersonService = NewPersonService(client)
	app.CommandService = NewCommandService(app.RefreshAll)
	app.PluginsService = NewPluginsService(client, "https://xbarapp.com/docs/plugins/")
	app.PluginsService.OnRefresh = app.RefreshAll
	app.defaultTrayMenu = &menu.TrayMenu{
		Label: "xbar",
		Menu:  app.newXbarMenu(nil, false),
	}
	return app, nil
}

func (app *app) Start(runtime *wails.Runtime) {
	app.setDarkMode(runtime.System.IsDarkMode())
	runtime.Events.OnThemeChange(func(darkMode bool) {
		// keep track of dark mode changing, and refresh all
		// plugins if it does.
		app.setDarkMode(darkMode)
		app.RefreshAll()
	})
	app.runtime = runtime
	app.PluginsService.runtime = runtime
	app.CommandService.runtime = runtime
	app.CommandService.clearCache = app.clearCache
	// ensure the plugin directory is there
	if err := os.MkdirAll(pluginDirectory, 0777); err != nil {
		log.Println("failed to create plugin directory:", err)
	}
	app.RefreshAll()
	go func() {
		// wait before checking for updates
		time.Sleep(10 * time.Second)
		for {
			app.checkForUpdates(true)
			// check again in twelve hours
			time.Sleep(12 * time.Hour)
		}
	}()
}

func (app *app) RefreshAll() {
	app.lock.Lock()
	defer app.lock.Unlock()
	if app.stopPluginsFunc != nil {
		// plugins are already running - let's stop them
		// and wait for them to stop
		app.stopPluginsFunc()
		<-app.pluginsStoppedSignal
	}
	// remove plugins
	if app.defaultTrayMenuActive {
		// only default menu - remove it
		app.runtime.Menu.DeleteTrayMenu(app.defaultTrayMenu)
		app.defaultTrayMenuActive = false
	}
	for _, plugin := range app.plugins {
		m, ok := app.pluginTrays[plugin.Command]
		if !ok {
			continue
		}
		app.runtime.Menu.DeleteTrayMenu(m)
	}
	var err error
	app.plugins, err = plugins.Dir(pluginDirectory)
	if err != nil {
		app.onErr(err.Error())
		return
	}
	app.pluginTrays = make(map[string]*menu.TrayMenu)
	if len(app.plugins) == 0 {
		// no plugins - use default
		app.runtime.Menu.SetTrayMenu(app.defaultTrayMenu)
		app.defaultTrayMenuActive = true
		return
	}
	for _, plugin := range app.plugins {
		// Setup plugin
		plugin.AppleScriptTemplate = app.settings.Terminal.AppleScriptTemplate3
		plugin.OnCycle = app.onCycle
		plugin.OnRefresh = app.onRefresh
		if app.Verbose {
			//plugin.Stdout = os.Stdout
			plugin.Stderr = os.Stderr
			plugin.Debugf = plugins.DebugfPrefix(plugin.CleanFilename(), plugins.DebugfLog)
		}
		app.pluginTrays[plugin.Command] = &menu.TrayMenu{
			Label:   " ",
			Menu:    app.newXbarMenu(plugin, false),
			OnOpen:  app.onMenuWillOpen,
			OnClose: app.onMenuDidClose,
		}
		app.runtime.Menu.SetTrayMenu(app.pluginTrays[plugin.Command])
	}
	app.pluginsStoppedSignal = make(chan struct{})
	var ctx context.Context
	ctx, app.stopPluginsFunc = context.WithCancel(context.Background())
	go func() {
		// use stopPluginsFunc to allow the context to
		// be canceled - which will kill all running plugin subprocesses.
		app.plugins.Run(ctx)
		close(app.pluginsStoppedSignal)
	}()
}

// CheckForUpdates proactively checks for updates.
func (app *app) CheckForUpdates() {
	app.checkForUpdates(false)
}

func (app *app) onMenuWillOpen() {
	app.lock.Lock()
	defer app.lock.Unlock()
	app.menuIsOpen = true
}

func (app *app) onMenuDidClose() {
	app.lock.Lock()
	defer app.lock.Unlock()
	app.menuIsOpen = false
}

func (app *app) updateStartOnLogin(data *menu.CallbackData) {
	err := mac.StartAtLogin(data.MenuItem.Checked)
	if err != nil {
		app.startsAtLoginMenu.Label = "Start at Login unavailable"
		app.startsAtLoginMenu.Disabled = true
	}
	// We need to refresh all as the menuitem is used in multiple places.
	// If we don't refresh, only the menuitem clicked will toggle in the UI.
	app.refreshMenus()
}

func (app *app) updateAutoupdate(data *menu.CallbackData) {
	if data.MenuItem.Checked && app.settings.AutoUpdate {
		// nothing to do
		return
	}
	if !data.MenuItem.Checked && !app.settings.AutoUpdate {
		// nothing to do
		return
	}
	app.settings.AutoUpdate = data.MenuItem.Checked
	if err := app.settings.save(); err != nil {
		log.Println("err: updateAutoupdate:", err)
		return
	}
	if app.settings.AutoUpdate {
		go app.checkForUpdates(true)
	}
	// We need to refresh all as the menuitem is used in multiple places.
	// If we don't refresh, only the menuitem clicked will toggle in the UI.
	app.refreshMenus()
}

// onErr adds a single menu showing the specified error
// string.
func (app *app) onErr(err string) {
	if app.defaultTrayMenuActive {
		app.runtime.Menu.DeleteTrayMenu(app.defaultTrayMenu)
		app.defaultTrayMenuActive = false
	}
	errorMenu := &menu.Menu{}
	errorMenu.Append(menu.Text(err, nil, nil))
	errorMenu.Append(menu.Separator())
	errorMenu.Merge(app.newXbarMenu(nil, false))
	app.defaultTrayMenu = &menu.TrayMenu{
		Label: "⚠️ xbar",
		Menu:  errorMenu,
	}
	app.runtime.Menu.SetTrayMenu(app.defaultTrayMenu)
	app.defaultTrayMenuActive = true
}

// Shutdown shuts down the app.
func (app *app) Shutdown() {
	if app.stopPluginsFunc != nil {
		// it's possible this gets called _before_ the stop
		// func is set. In which case, we'll just ignore it.
		app.stopPluginsFunc()
	}
}

func (app *app) newXbarMenu(plugin *plugins.Plugin, asSubmenu bool) *menu.Menu {
	var items []*menu.MenuItem
	if plugin != nil {
		items = append(items, &menu.MenuItem{
			Type:        menu.TextType,
			Label:       "Refresh",
			Accelerator: keys.CmdOrCtrl("r"),
			Click: func(ctx *menu.CallbackData) {
				app.onPluginsRefreshMenuClicked(ctx, plugin)
			},
		})
	}
	items = append(items, &menu.MenuItem{
		Type:        menu.TextType,
		Label:       "Refresh All",
		Accelerator: keys.Combo("r", keys.CmdOrCtrlKey, keys.ShiftKey),
		Click:       app.onPluginsRefreshAllMenuClicked,
	})
	if plugin != nil {
		items = append(items, menu.Text("Run in Terminal…", keys.CmdOrCtrl("t"), func(_ *menu.CallbackData) {
			err := plugin.RunInTerminal(app.settings.Terminal.AppleScriptTemplate3)
			if err != nil {
				_, err2 := app.runtime.Dialog.Message(&dialog.MessageDialog{
					Type:         dialog.ErrorDialog,
					Title:        "Run in Terminal",
					Message:      err.Error(),
					Buttons:      []string{"OK"},
					CancelButton: "OK",
				})
				if err2 != nil {
					log.Println(err2)
					return
				}
				return
			}
		}))
	}
	items = append(items, menu.Separator())
	if plugin != nil {
		items = append(items, &menu.MenuItem{
			Type:        menu.TextType,
			Label:       "Open Plugin…",
			Accelerator: keys.CmdOrCtrl("e"),
			Click: func(_ *menu.CallbackData) {
				app.runtime.Window.Show()
				rel, err := filepath.Rel(pluginDirectory, plugin.Command)
				if err != nil {
					log.Println(err)
					return
				}
				app.runtime.Events.Emit("xbar.browser.openInstalledPlugin", map[string]string{
					"path": rel,
				})
			},
		})
	}
	items = append(items, &menu.MenuItem{
		Type:        menu.TextType,
		Label:       "Plugin Browser…",
		Accelerator: keys.CmdOrCtrl("p"),
		Click:       app.onPluginsMenuClicked,
	})
	items = append(items, &menu.MenuItem{
		Type:  menu.TextType,
		Label: "Open Plugin Folder…",
		Click: app.onOpenPluginsFolderClicked,
	})
	items = append(items, menu.Separator())
	items = append(items, &menu.MenuItem{
		Type:     menu.TextType,
		Label:    fmt.Sprintf("xbar (%s)", version),
		Disabled: true,
	})
	items = append(items, app.appUpdatesMenu)
	items = append(items, app.autoupdateMenu)
	items = append(items, app.startsAtLoginMenu)
	items = append(items, menu.Separator())
	items = append(items, &menu.MenuItem{
		Type:        menu.TextType,
		Label:       "Quit xbar",
		Accelerator: keys.CmdOrCtrl("q"),
		Click:       app.onQuitMenuClicked,
	})
	if asSubmenu {
		m := menu.NewMenuFromItems(
			menu.SubMenu("xbar", &menu.Menu{
				Items: items,
			}),
		)
		return m
	}
	m := &menu.Menu{Items: items}
	return m
}

func (app *app) createDefaultMenus() {
	app.defaultTrayMenu = &menu.TrayMenu{
		Label: "xbar",
		Menu:  app.newXbarMenu(nil, false),
	}
}

func (app *app) onPluginsMenuClicked(_ *menu.CallbackData) {
	app.runtime.Window.Show()
}

func (app *app) onOpenPluginsFolderClicked(_ *menu.CallbackData) {
	_ = app.CommandService.OpenPath(pluginDirectory)
}

func (app *app) onQuitMenuClicked(_ *menu.CallbackData) {
	app.runtime.Quit()
}

func (app *app) onPluginsRefreshMenuClicked(_ *menu.CallbackData, p *plugins.Plugin) {
	p.TriggerRefresh()
}

func (app *app) onPluginsRefreshAllMenuClicked(_ *menu.CallbackData) {
	app.RefreshAll()
}

func (app *app) onBrowserRefreshMenuClicked(_ *menu.CallbackData) {
	app.runtime.Events.Emit("xbar.browser.refresh")
}

func (app *app) onBrowserHardRefreshMenuClicked(_ *menu.CallbackData) {
	app.clearCache(true)
	app.runtime.Events.Emit("xbar.browser.refresh")
}

func (app *app) onCheckForUpdatesMenuClick(_ *menu.CallbackData) {
	app.CheckForUpdates()
}

func (app *app) onClearCacheMenuClicked(_ *menu.CallbackData) {
	app.clearCache(false)
}

func (app *app) clearCache(passive bool) {
	// bit cheeky - but use the oslock in PluginsService to protect
	// against this from being run concurrently.
	app.PluginsService.osLock.Lock()
	defer app.PluginsService.osLock.Unlock()
	err := os.RemoveAll(cacheDirectory)
	if err != nil {
		if passive {
			return
		}
		_, err2 := app.runtime.Dialog.Message(&dialog.MessageDialog{
			Type:         dialog.ErrorDialog,
			Title:        "Clear cache failed",
			Message:      err.Error(),
			Buttons:      []string{"OK"},
			CancelButton: "OK",
		})
		if err2 != nil {
			log.Println(err2)
			return
		}
		return
	}
	if passive {
		return
	}
	_, err2 := app.runtime.Dialog.Message(&dialog.MessageDialog{
		Type:         dialog.InfoDialog,
		Title:        "Cache cleared",
		Message:      "The local cache was successfully cleared.",
		Buttons:      []string{"OK"},
		CancelButton: "OK",
	})
	if err2 != nil {
		log.Println(err2)
		return
	}
}

func (app *app) handleIncomingURL(url string) {
	// wait for a space
	app.incomingURLSemaphore <- struct{}{}
	defer func() {
		// free up this space
		<-app.incomingURLSemaphore
	}()
	log.Println("incoming URL: handleIncomingURL", url)
	incomingURL, err := parseIncomingURL(url)
	if err != nil {
		_, err2 := app.runtime.Dialog.Message(&dialog.MessageDialog{
			Type:         dialog.ErrorDialog,
			Title:        "Invalid URL",
			Message:      err.Error(),
			Buttons:      []string{"OK"},
			CancelButton: "OK",
		})
		if err2 != nil {
			log.Println(err2)
			return
		}
		return
	}
	switch incomingURL.Action {
	case "openPlugin":
		app.runtime.Window.Show()
		app.runtime.Events.Emit("xbar.incomingURL.openPlugin", map[string]string{
			"path": incomingURL.Params.Get("path"),
		})
	case "refreshPlugin":
		for _, plugin := range app.plugins {
			rel, err := filepath.Rel(pluginDirectory, plugin.Command)
			if err != nil {
				log.Println("incoming URL: rel for this failed", err)
				continue
			}
			if rel == incomingURL.Params.Get("path") {
				plugin.TriggerRefresh()
				return
			}
		}
	case "refreshAllPlugins":
		app.RefreshAll()
	default:
		log.Printf("incoming URL: skipping, unknown action %q\n", incomingURL.Action)
	}
}

// onRefresh is fired when a plugin needs to refresh.
func (app *app) onRefresh(ctx context.Context, p *plugins.Plugin, _ error) {
	app.lock.Lock()
	defer app.lock.Unlock()
	if app.menuIsOpen {
		// don't update while the menu is open
		// as this can cause a crash
		return
	}
	tray, ok := app.pluginTrays[p.Command]
	if !ok {
		log.Println("no item - probably refreshing", tray.Label)
		return
	}
	app.updateLabel(tray, p)
	pluginMenu := app.menuParser.ParseItems(ctx, p.Items.ExpandedItems)
	if pluginMenu == nil {
		pluginMenu = app.newXbarMenu(p, false)
	} else {
		pluginMenu.Append(menu.Separator())
		pluginMenu.Merge(app.newXbarMenu(p, true))
	}
	tray.Menu = pluginMenu
	app.runtime.Menu.SetTrayMenu(tray)
}

func (app *app) onCycle(_ context.Context, p *plugins.Plugin) {
	app.lock.Lock()
	defer app.lock.Unlock()
	if app.menuIsOpen {
		// don't update while the menu is open
		// as this can cause a crash
		return
	}
	tray, ok := app.pluginTrays[p.Command]
	if !ok {
		// no tray item - it's probably refreshing
		// so we'll just skip silently.
		return
	}
	if app.updateLabel(tray, p) {
		app.runtime.Menu.UpdateTrayMenuLabel(tray)
	}
}

// refreshMenus refreshes all tray menus to ensure they
// are in sync with the data, EG: checkbox sync
func (app *app) refreshMenus() {
	app.onMenuWillOpen()
	for _, tray := range app.pluginTrays {
		app.runtime.Menu.SetTrayMenu(tray)
	}
	app.onMenuDidClose()
}

func (app *app) updateLabel(tray *menu.TrayMenu, p *plugins.Plugin) bool {
	cycleItem := p.CurrentCycleItem()
	if cycleItem == nil {
		return false
	}
	tray.Label = cycleItem.DisplayText()
	tray.Image = cycleItem.Params.Image
	tray.FontName = cycleItem.Params.Font
	tray.FontSize = cycleItem.Params.Size
	tray.RGBA = cycleItem.Params.Color
	tray.Disabled = cycleItem.Params.Disabled
	if cycleItem.Params.TemplateImage != "" {
		tray.Image = cycleItem.Params.TemplateImage
		tray.MacTemplateImage = true
	}

	return true
}

// checkForUpdates looks to see if there's a newer version of xbar,
// downloads it and installs it.
// If passive is true, it won't complain if it fails.
func (app *app) checkForUpdates(passive bool) {
	if app.Verbose {
		log.Printf("checking for updates... (current: %s)", version)
		log.Println("updates: passive", passive)
		log.Println("updates: AutoUpdate", app.settings.AutoUpdate)
	}
	u := update.Updater{
		CurrentVersion: version,
		//LatestReleaseGitHubEndpoint: "https://api.github.com/repos/matryer/xbar/releases/latest",
		LatestReleaseGitHubEndpoint: "https://api.github.com/repos/matryer/xbar/releases/latest",
		Client:                      &http.Client{Timeout: 10 * time.Minute},
		SelectAsset: func(release update.Release, asset update.Asset) bool {
			// look for the zip file
			return filepath.Ext(asset.Name) == ".zip"
		},
		DownloadBytesLimit: 10_741_824, // 10MB
	}
	latest, hasUpdate, err := u.HasUpdate()
	if err != nil {
		if app.Verbose {
			log.Println("failed to check for updates:", err)
		}
		if !passive {
			_, err := app.runtime.Dialog.Message(&dialog.MessageDialog{
				Type:         dialog.ErrorDialog,
				Title:        "Update check failed",
				Message:      err.Error(),
				Buttons:      []string{"OK"},
				CancelButton: "OK",
			})
			if err != nil {
				log.Println(err)
				return
			}
		}
		return
	}
	if !hasUpdate {
		// they are using the latest version
		if app.Verbose {
			log.Println("update: you have the latest version")
		}
		if !passive {
			_, err := app.runtime.Dialog.Message(&dialog.MessageDialog{
				Type:         dialog.InfoDialog,
				Title:        "You're up to date",
				Message:      fmt.Sprintf("%s is the latest version.", latest.TagName),
				Buttons:      []string{"OK"},
				CancelButton: "OK",
			})
			if err != nil {
				log.Println(err)
				return
			}
		}
		return
	}
	if !app.settings.AutoUpdate {
		oneWeek := 168 * time.Hour
		// if this check is passive, and the release is only a few days
		// old - do a soft prompt.
		if passive && latest.CreatedAt.After(time.Now().Add(0-oneWeek)) {
			// Update menu text
			app.appUpdatesMenu.Label = "Install " + latest.TagName + "…"
			app.refreshMenus()
			return
		}
		response, err := app.runtime.Dialog.Message(&dialog.MessageDialog{
			Type:          dialog.QuestionDialog,
			Title:         "Update xbar?",
			Message:       fmt.Sprintf("xbar %s is now available (you have %s).\n\nWould you like to update?", latest.TagName, u.CurrentVersion),
			Buttons:       []string{"Update", "Later"},
			DefaultButton: "Update",
			CancelButton:  "Later",
		})
		if err != nil {
			log.Println(err)
			return
		}
		switch response {
		case "Update":
			// continue
		case "Later":
			return
		}
	} else {
		if app.Verbose {
			log.Println("autoupdating...")
		}
	}
	_, err = u.Update()
	if err != nil {
		if app.Verbose {
			log.Println("failed to update:", err)
		}
		if !passive {
			_, err := app.runtime.Dialog.Message(&dialog.MessageDialog{
				Type:         dialog.InfoDialog,
				Title:        "Update successful",
				Message:      "Please restart xbar for the changes to take effect.",
				Buttons:      []string{"OK"},
				CancelButton: "OK",
			})
			if err != nil {
				log.Println(err)
				return
			}
		}
		return
	}
	err = u.Restart()
	if err != nil {
		if app.Verbose {
			log.Println("failed to restart:", err)
		}
		if !passive {
			_, err := app.runtime.Dialog.Message(&dialog.MessageDialog{
				Type:         dialog.InfoDialog,
				Title:        "Update successful",
				Message:      "Please restart xbar for the changes to take effect.",
				Buttons:      []string{"OK"},
				CancelButton: "OK",
			})
			if err != nil {
				log.Println(err)
				return
			}
		}
		return
	}
}

// tickOS waits a beat after some os changes to give the system
// time to reflect those changes.
func tickOS() {
	time.Sleep(500 * time.Millisecond)
}

// setDarkMode sets the current dark mode state.
// It updates app.isDarkMode and also sets the
// appropriate environment variables.
func (app *app) setDarkMode(darkmode bool) {
	app.lock.Lock()
	defer app.lock.Unlock()
	app.isDarkMode = darkmode
	var err error
	if darkmode {
		err = os.Setenv("BitBarDarkMode", "true") // backwards compatibility
		if err != nil {
			log.Println("os.Setenv", err)
		}
		err = os.Setenv("XBARDarkMode", "true")
		if err != nil {
			log.Println("os.Setenv", err)
		}
	} else {
		err = os.Setenv("BitBarDarkMode", "false") // backwards compatibility
		if err != nil {
			log.Println("os.Setenv", err)
		}
		err = os.Setenv("XBARDarkMode", "false")
		if err != nil {
			log.Println("os.Setenv", err)
		}
	}
}


================================================
FILE: app/build.sh
================================================
#!/bin/bash
set -e

VERSION=`git describe --tags`

echo ""
echo "  xbar ${VERSION}..."
echo ""
echo -n $VERSION > .version

wails build -o xbar


================================================
FILE: app/categories_service.go
================================================
package main

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"net/http"
)

// Category represents a group of plugins.
type Category struct {
	Path                 string     `json:"path"`
	Text                 string     `json:"text"`
	Children             []Category `json:"children"`
	CategoryPathSegments []PathItem `json:"categoryPathSegments"`
}

// PathItem is a path segment.
type PathItem struct {
	Path   string `json:"path"`
	Text   string `json:"text"`
	IsLast bool   `json:"isLast"`
}

// CategoriesService access category information.
type CategoriesService struct {
	baseURL string
	client  *http.Client
}

// NewCategoriesService makes a new CategoriesService.
func NewCategoriesService(client *http.Client) *CategoriesService {
	return &CategoriesService{
		baseURL: "https://xbarapp.com/docs",
		client:  client,
	}
}

// GetCategories gets the categories from the remote server.
func (c *CategoriesService) GetCategories() ([]Category, error) {
	req, err := http.NewRequest("GET", c.baseURL+"/plugins/categories.json", nil)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(req.Context(), apiRequestTimeout)
	defer cancel()
	req = req.WithContext(ctx)
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	var payload struct {
		Categories []Category `json:"categories"`
	}
	err = json.Unmarshal(body, &payload)
	if err != nil {
		return nil, err
	}
	return payload.Categories, nil
}


================================================
FILE: app/categories_service_test.go
================================================
package main

import (
	"net/http"
	"testing"
	"time"

	"github.com/matryer/is"
)

func TestCategoryRepositoryGetCategories(t *testing.T) {
	is := is.New(t)

	cr := NewCategoriesService(&http.Client{Timeout: 1 * time.Second})
	cats, err := cr.GetCategories()
	is.NoErr(err)
	is.True(len(cats) > 0)

	cr.baseURL = "broken"
	cats, err = cr.GetCategories()
	is.True(err != nil)
	is.True(cats == nil)
}


================================================
FILE: app/command_service.go
================================================
package main

import (
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	"github.com/pkg/errors"
	wails "github.com/wailsapp/wails/v2"
)

// CommandService provides window service.
type CommandService struct {
	runtime    *wails.Runtime
	OnRefresh  func()
	clearCache func(passive bool)
}

// NewCommandService makes a new CommandService.
func NewCommandService(OnRefresh func()) *CommandService {
	return &CommandService{
		OnRefresh: OnRefresh,
	}
}

// ClearCache clears the cache.
func (c *CommandService) ClearCache() {
	c.clearCache(false)
}

// RefreshAllPlugins refreshes all plugins.
func (c *CommandService) RefreshAllPlugins() {
	c.OnRefresh()
}

// WindowHide hides the window.
func (c *CommandService) WindowHide() {
	c.runtime.Window.Hide()
}

// WindowMinimise minimises the window.
func (c *CommandService) WindowMinimise() {
	c.runtime.Window.Minimise()
}

// OpenPath opens a window.
func (c CommandService) OpenPath(path string) error {
	err := c.runCommand("open", path)
	if err != nil {
		return errors.Wrapf(err, "unable to open path %q", path)
	}
	return nil
}

// OpenURL opens a window.
func (c CommandService) OpenURL(url string) error {
	err := c.runCommand("open", url)
	if err != nil {
		return errors.Wrapf(err, "unable to open URL %s", url)
	}
	return nil
}

// OpenFile opens a file for editing.
func (c CommandService) OpenFile(path string) error {
	err := c.runCommand("open", filepath.Join(pluginDirectory, path))
	if err != nil {
		return errors.Wrapf(err, "failed to open %q", path)
	}
	return nil
}

// runCommand runs the command, wiring up stdout and stderr, and
// inheriting the environment.
func (CommandService) runCommand(name string, args ...string) error {
	cmd := exec.Command(name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
	}
	cmd.Env = os.Environ()
	return cmd.Run()
}


================================================
FILE: app/frontend/.gitignore
================================================
/node_modules/
/public/build/

.DS_Store
/public/bundle.js.map
/public/bundle.js
/public/bundle.css


================================================
FILE: app/frontend/package.json
================================================
{
  "name": "xbar",
  "version": "1.0.0",
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public",
    "start:dev": "sirv public --single --host 0.0.0.0 --dev"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^11.0.0",
    "@rollup/plugin-image": "^2.0.6",
    "@rollup/plugin-node-resolve": "^7.0.0",
    "@rollup/plugin-url": "^5.0.1",
    "@wails/runtime": "^1.3.10",
    "autoprefixer": "^10.4.14",
    "focus-visible": "^5.2.0",
    "highlight.js": ">=10.4.1",
    "postcss": "^8.4.21",
    "postcss-import": "^15.1.0",
    "rollup": "^2.35.1",
    "rollup-plugin-livereload": "^1.0.0",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-string": "^3.0.0",
    "rollup-plugin-svelte": "~6.1.1",
    "rollup-plugin-terser": "^5.1.2",
    "sirv-cli": "^0.4.4",
    "svelte": "^3.32.2",
    "svelte-highlight": "^0.6.2",
    "svelte-preprocess": "^4.6.1",
    "tailwindcss": "^3.3.1"
  },
  "dependencies": {
    "svelte-hash-router": "^1.0.1"
  }
}


================================================
FILE: app/frontend/postcss.config.js
================================================
module.exports = {
    plugins: [
        require('postcss-import'),
        require('tailwindcss'),
        require('autoprefixer'),
    ],
}

================================================
FILE: app/frontend/public/index.html
================================================
<!doctype html>
<html lang="en">
<head>
	<meta charset="utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
	<meta name="viewport" content="width=device-width,initial-scale=1" />
	<meta name="mobile-web-app-capable" content="yes" />
	<meta name="apple-mobile-web-app-capable" content="yes" />
	<meta name="apple-mobile-web-app-status-bar-style" content="black" />
	<meta name="apple-mobile-web-app-title" content="svelte-mui" />
	<meta name="application-name" content="svelte-mui" />
	<meta name="theme-color" content="#212121" />
	<title>xbar</title>
	<link rel='stylesheet' href='/bundle.css'>
	<script defer src='/bundle.js'></script>
</head>
<body>
	<noscript>xbar requires JavaScript to run correctly</noscript>
</body>
</html>


================================================
FILE: app/frontend/rollup.config.js
================================================
import svelte from 'rollup-plugin-svelte'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import livereload from 'rollup-plugin-livereload'
import { terser } from 'rollup-plugin-terser'
import postcss from 'rollup-plugin-postcss'
import autoPreprocess from 'svelte-preprocess'
import image from '@rollup/plugin-image'
import { string } from 'rollup-plugin-string'
import url from '@rollup/plugin-url'

const production = !process.env.ROLLUP_WATCH;

export default {
	input: 'src/main.js',
	output: {
		sourcemap: true,
		format: 'iife',
		name: 'app',
		file: 'public/bundle.js'
	},
	onwarn: handleRollupWarning,
	plugins: [

		image(),

		// Embed binary files
		url({
			include: ['**/*.woff', '**/*.woff2'],
			limit: Infinity,
		}),

		// Embed text files
		string({
			include: ["**/*.jsx", "**/*.go", "**/*.txt"],
		}),

		svelte({
			preprocess: autoPreprocess(),
			// enable run-time checks when not in production
			dev: !production,
			// we'll extract any component CSS out into
			// a separate file - better for performance
			// css: css => {
			// 	css.write('bundle.css');
			// },
			emitCss: true,
		}),

		// If you have external dependencies installed from
		// npm, you'll most likely need these plugins. In
		// some cases you'll need additional configuration -
		// consult the documentation for details:
		// https://github.com/rollup/plugins/tree/master/packages/commonjs
		resolve({
			browser: true,
			dedupe: ['svelte']
		}),
		commonjs(),

		// PostCSS preprocessing
		postcss({
			extensions: ['.css', '.scss'],
			extract: true,
			minimize: false,
			use: [
				['sass', {
					includePaths: [
						'./src/theme',
						'./node_modules'
					]
				}]
			],
		}),

		// In dev mode, call `npm run start` once
		// the bundle has been generated
		!production && serve(),

		// Watch the `public` directory and refresh the
		// browser on changes when not in production
		!production && livereload('public'),

		// If we're building for production (npm run build
		// instead of npm run dev), minify
		production && terser()
	],
	watch: {
		clearScreen: false
	}
};

function handleRollupWarning(warning) {
	console.error('ERROR: ' + warning.toString());
}

function serve() {
	let started = false;

	return {
		writeBundle() {
			if (!started) {
				started = true;

				require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
					stdio: ['ignore', 'inherit', 'inherit'],
					shell: true
				});
			}
		}
	};
}


================================================
FILE: app/frontend/src/App.svelte
================================================
<script>

	import { Router } from 'svelte-hash-router'
	import { Events } from '@wails/runtime'
	import { 
			categories, selectedCategoryPath, selectCategory,
			installedPlugins, selectedInstalledPluginPath, selectInstalledPlugin,
	} from './pagedata.svelte'
	import { 
			refreshCategories, refreshInstalledPlugins, 
			openURL, refreshAllPlugins, clearCache,
	 } from './rpc.svelte'
	import A from './elements/A.svelte'
	import Button from './elements/Button.svelte'
	import Error from './elements/Error.svelte'
	import { globalWaiter, wait } from './waiters.svelte'
	import KeyboardShortcuts from './elements/KeyboardShortcuts.svelte'
    import { sigRefresh, fireSigRefresh } from './signals.svelte'

	let err;

	$: installedPluginsEnabled = $installedPlugins ? $installedPlugins.filter(p => p.enabled) : []
	$: installedPluginsDisabled = $installedPlugins ? $installedPlugins.filter(p => !p.enabled) : []

	Events.On('xbar.browser.refresh', function(){
		fireSigRefresh()
	})

	Events.On('xbar.incomingURL.openPlugin', function(params){
		location.hash = `/plugin-details/${params.path}`
	})
	
	Events.On('xbar.browser.openInstalledPlugin', function(params){
		location.hash = `/installed-plugins/${params.path}`
	})

	$: if ($sigRefresh) {
		const done = wait()
		const done2 = wait()
		refreshCategories(categories)
			.catch(e => err = e)
			.finally(() => done())

		refreshInstalledPlugins(installedPlugins)
			.catch(e => err = e)
			.finally(() => done2())
	}

	function openSponsorPage() {
		openURL('https://github.com/sponsors/matryer')
			.catch(e => err = e)
	}
	
	function openBugPage() {
		openURL('https://github.com/matryer/xbar/issues')
			.catch(e => err = e)
	}

	function openPluginGuide(event) {
		event.preventDefault()
		openURL('https://github.com/matryer/xbar-plugins/blob/main/CONTRIBUTING.md')
			.catch(e => err = e)
	}

	// refresh will use fireSigRefresh to trigger refresh.
	function onRefreshClick() {
		refreshAllPlugins()
			.finally(() => {
				fireSigRefresh()
			})
	}

</script>

<style global>
	@import './styles.css';
	.top-bar {
		height: 50px;
	}
	.side-bar {
		width: 200px;
	}
</style>
<svelte:body 
	class='noselect'
></svelte:body>

<KeyboardShortcuts />

<Error err={err} />

<div class='flex h-full'>
	<div class='side-bar flex-shrink-0 flex flex-col h-full bg-gray-400/25 text-gray-700 dark:text-gray-300'>
		<div 
			data-wails-drag 
			class='top-bar flex-shrink-0 flex justify-end p-4'
		>
			<div class='text-gray-400 dark:text-gray-300'>
				BETA
			</div>
		</div>
		<div class='p-3 pt-0 overflow-y-scroll overscroll-none overflow-x-hidden'>
			{#if $installedPlugins}
				{#if installedPluginsEnabled.length}
					<h2 class='text-sm text-gray-500 dark:text-gray-400 text-bold mb-1'>
						Plugins
					</h2>
					<div class='mb-4'>
						{#each installedPluginsEnabled as installedPlugin}
							<a
								class='truncate block px-3 py-1 rounded text-gray-600 dark:text-gray-200 bg-opacity-25'
								class:bg-gray-400={$selectedInstalledPluginPath==installedPlugin.path}
								href='#/installed-plugins/{installedPlugin.path}'
								on:click|preventDefault='{ () => selectInstalledPlugin(installedPlugin.path) }'
							>
								{installedPlugin.name}
							</a>
						{/each}
					</div>
				{/if}
				{#if installedPluginsDisabled.length}
					<h2 class='text-sm text-gray-500 dark:text-gray-400 text-bold mb-1'>
						Disabled plugins
					</h2>
					<div class='mb-4'>
						{#each installedPluginsDisabled as installedPlugin}
							<a
								class='opacity-75 truncate block px-3 py-1 rounded text-gray-600 dark:text-gray-200 bg-opacity-25'
								class:bg-gray-400={$selectedInstalledPluginPath==installedPlugin.path}
								href='#/installed-plugins/{installedPlugin.path}'
								on:click|preventDefault='{ () => selectInstalledPlugin(installedPlugin.path) }'
							>
								{installedPlugin.name}
							</a>
						{/each}
					</div>
				{/if}
			{/if}
			{#if $categories}
				<h2 class='text-sm text-gray-500 dark:text-gray-400 text-bold mb-1'>
					Categories
				</h2>	
				{#each $categories as category}
					<a
						class='truncate block px-3 py-1 rounded text-gray-600 dark:text-gray-200 bg-opacity-25'
						class:bg-gray-400={$selectedCategoryPath==category.path}
						href='#/plugins/{category.path}'
						on:click|preventDefault='{ () => selectCategory(category.path) }'
					>
						{category.text}
					</a>
				{/each}
			{:else}
				&hellip;
			{/if}
			<p class='p-3 mt-8'>
				<strong>Got an idea of your own?</strong>
				Read our handy <A underline={true} on:click={ openPluginGuide }>Plugin guide</A>.
			</p>
		</div>
	</div>
	<div class='flex-grow h-full flex flex-col bg-gray-200/25 text-gray-700 dark:text-gray-300'>
		<div class='top-bar flex-shrink-0 px-6 py-3 flex items-center space-x-5' data-wails-drag>
			<div data-wails-no-drag data-wails-context-menu-id="refreshContextMenu">
				<Button 
					waiter={$globalWaiter}
					on:click='{ onRefreshClick }'
					cssclass='py-1 select-none'
				>↺</Button>
			</div>
			<div class='flex-grow' ></div>
			<div>
				<Button on:click='{ openBugPage }'>
					Report bug
				</Button>
			</div>
			<div>
				<Button on:click='{ openSponsorPage }'>
					♥ Sponsor
				</Button>
			</div>
		</div>
		<div class='flex-1 overflow-y-scroll overscroll-none overflow-x-hidden'>
			<Router />
		</div>
	</div>
</div>


================================================
FILE: app/frontend/src/Homepage.svelte
================================================
<script>

	import { onMount } from 'svelte'
	import { getFeaturedPlugins, openURL } from './rpc.svelte'
	import A from './elements/A.svelte'
	import Error from './elements/Error.svelte'
	import PluginCollection from './elements/PluginCollection.svelte'
	import { wait } from './waiters.svelte'
	import { installedPlugins, clearNav } from './pagedata.svelte'
	
	let featuredPlugins
	let err
	onMount(() => {
		clearNav()
		const done = wait()
		getFeaturedPlugins()
			.then(plugins => featuredPlugins = plugins)
			.catch(e => err = e)
			.finally(() => done())
	})

	function openGetInTouchWindow() {
		openURL('https://twitter.com/matryer')
			.catch(e => err = e)
	}

</script>
<Error err={err} />
<div class='px-6 py-3'>
	<h2 class='uppercase text-sm text-gray-500 dark:text-gray-300'>
		Featured plugins
	</h2>
</div>
<PluginCollection plugins={featuredPlugins} />
<div class='p-6 pt-0'>
	<strong>
		Get featured:
	</strong>
	<A cssclass='underline' on:click={ () => openGetInTouchWindow() }>
		Get in touch
	</A>
</div>


================================================
FILE: app/frontend/src/InstalledPluginView.svelte
================================================
<script>
	import { params } from 'svelte-hash-router';
	import {
		uninstallPlugin,
		refreshInstalledPlugins,
		getInstalledPluginMetadata,
		loadVariableValues,
		saveVariableValues,
		setEnabled,
		setRefreshInterval,
		openURL,
		openFile,
	} from './rpc.svelte';
	import {
		installedPlugins,
		selectedInstalledPluginPath,
		clearNav,
	} from './pagedata.svelte';
	import { wait } from './waiters.svelte';
	import Breadcrumbs from './elements/Breadcrumbs.svelte';
	import PluginDetails from './elements/PluginDetails.svelte';
	import Variables from './elements/Variables.svelte';
	import PluginSourceBrowser from './elements/PluginSourceBrowser.svelte';
	import Button from './elements/Button.svelte';
	import Error from './elements/Error.svelte';
	import Switch from './elements/Switch.svelte';
	import Spinner from './elements/Spinner.svelte';
	import Duration from './elements/Duration.svelte';
	import { sigRefresh } from './signals.svelte';

	let err;

	let installedPlugin = null;
	let refreshInterval;
	let variableValues = null;

	$: if ($sigRefresh && $params._) {
		loadPluginMetadata($params._);
	}

	function loadPluginMetadata(installedPluginPath) {
		if (!installedPluginPath) {
			return;
		}
		err = null;
		const done1 = wait();
		const done2 = wait();
		getInstalledPluginMetadata(installedPluginPath)
			.then((result) => {
				installedPlugin = result.plugin;
				installedPlugin.enabled = result.enabled;
				refreshInterval = result.refreshInterval;
			})
			.catch((e) => (err = e))
			.finally(() => done1());
		loadVariableValues(installedPluginPath)
			.then((result) => (variableValues = result))
			.catch((e) => (err = e))
			.finally(() => done2());
	}

	$: updateValues(
		installedPlugin ? installedPlugin.vars : null,
		variableValues
	);

	function updateValues(variables, values) {
		if (!variables || !values) return; // wait for both
		variables.forEach((v) => {
			if (typeof values[v.name] !== 'undefined') {
				return;
			}
			switch (v.type) {
				case 'string':
				case 'select':
					values[v.name] = v.default;
					break;
				case 'boolean':
					const def = v.default || '';
					values[v.name] = def.toUpperCase() == 'TRUE';
					break;
				case 'number':
					values[v.name] = parseFloat(v.default) || 0;
					break;
				default:
					console.warn(
						`Variables: ${v.name} has unsupported type ${v.type} (skipping)`
					);
			}
		});
	}

	function onValuesChanged() {
		saveVariableValues(installedPlugin.path, variableValues);
	}

	let pluginEnableToggleWaiter = 0;
	function toggleEnabled() {
		pluginEnableToggleWaiter++;
		installedPlugin.enabled = !installedPlugin.enabled;
		setEnabled(installedPlugin.path, installedPlugin.enabled)
			.then((updatedPath) => {
				// redirect to new place
				selectedInstalledPluginPath.set(updatedPath);
				location.hash = `/installed-plugins/${updatedPath}`;
				refreshInstalledPlugins(installedPlugins);
			})
			.catch((e) => (err = e))
			.finally(() => pluginEnableToggleWaiter--);
	}

	function onDurationChanged() {
		const done = wait();
		setRefreshInterval(installedPlugin.path, refreshInterval)
			.then((result) => {
				// redirect to new place
				selectedInstalledPluginPath.set(result.installedPluginPath);
				location.hash = `/installed-plugins/${result.installedPluginPath}`;
				refreshInstalledPlugins(installedPlugins);
			})
			.catch((e) => (err = e))
			.finally(() => done());
	}

	function onUninstallClick() {
		const done = wait();
		uninstallPlugin(installedPlugin)
			.then((result) => {
				if (result === false) {
					// canceled
					return;
				}
				clearNav();
				location.hash = '';
				refreshInstalledPlugins(installedPlugins);
			})
			.catch((e) => (err = e))
			.finally(() => done());
	}

	function gotoOpenPluginIssue(plugin) {
		let body = ``;
		if (plugin.authors) {
			const githubUsernamesList = plugin.authors
				.map((author) => author.githubUsername)
				.filter(
					(githubUsername) => githubUsername && githubUsername != ''
				)
				.join(', ');
			if (plugin.authors.length > 0) {
				body = `fao @${githubUsernamesList} - ${body}`;
			}
		}
		if (body != '') {
			body = `${body}

`; // line feeds
		}
		const path = `https://github.com/matryer/xbar-plugins/issues/new?body=${encodeURIComponent(
			body
		)}&title=${encodeURIComponent(plugin.path + ': ')}`;
		openURL(path).catch((e) => (err = e));
	}

	let openEditorErr = null;
	function openEditor(e) {
		openFile(installedPlugin.path).catch((e) => (openEditorErr = e));
	}
</script>

<Error err="{err}" />

{#if !err}
	<Breadcrumbs>
		{#if installedPlugin}
			<strong>
				{installedPlugin.title || installedPlugin.filename}
			</strong>
		{/if}
	</Breadcrumbs>

	<div class="flex flex-col h-full max-w-full">
		<div class="p-6 space-x-8">
			<div class="flex space-x-8">
				<PluginDetails plugin="{installedPlugin}">
					<div slot="action" class="pl-3">
						<Spinner
							width="16"
							height="16"
							waiter="{pluginEnableToggleWaiter}"
						/>
						<Switch
							on="{installedPlugin.enabled}"
							loading="{pluginEnableToggleWaiter > 0}"
							on:click="{toggleEnabled}"
						/>
					</div>
					<div slot="footer">
						{#if refreshInterval}
							<div class="p-4 text-right">
								<span class="mr-1">Refresh every:</span>
								<Duration
									value="{refreshInterval}"
									on:change="{onDurationChanged}"
									disabled="{!installedPlugin.enabled}"
								/>
							</div>
						{/if}
					</div>
				</PluginDetails>
				{#if installedPlugin && installedPlugin.imageURL}
					<div class="flex-shrink mb-8">
						<img
							alt="Screenshot of {installedPlugin.title}"
							src="{installedPlugin.imageURL}"
							onerror="this.style.display='none'"
							class="plugin-image max-h-64"
						/>
					</div>
				{/if}
			</div>
			{#if installedPlugin}
				<div class="p-3 flex space-x-5">
					<Button
						on:click="{() => gotoOpenPluginIssue(installedPlugin)}"
					>
						Open issue&hellip;
					</Button>
					<Button on:click="{onUninstallClick}">
						Uninstall this plugin
					</Button>
				</div>
			{/if}
		</div>
		{#if installedPlugin && installedPlugin.vars && installedPlugin.enabled}
			<div class="shadow-lg dark:shadow-none">
				<Variables
					on:change="{onValuesChanged}"
					variables="{installedPlugin.vars}"
					values="{variableValues}"
					disabled="{!installedPlugin.enabled}"
				/>
			</div>
		{/if}
		{#if installedPlugin}
			<Error err="{openEditorErr}" />
			<div
				class="flex-grow bg-white/75 dark:bg-gray-700/75 p-3 border-t border-gray-200 dark:border-gray-600"
			>
				<PluginSourceBrowser
					showEditButton="{true}"
					on:openEditor="{openEditor}"
					files="{installedPlugin.files}"
				/>
			</div>
		{/if}
	</div>
{/if}

<style>
	.plugin-image {
		max-width: 300px;
	}
</style>


================================================
FILE: app/frontend/src/PersonView.svelte
================================================
<script>

	import { params } from 'svelte-hash-router'
	import { getPersonDetails, openURL } from './rpc.svelte'
	import { wait } from './waiters.svelte'
	import Error from './elements/Error.svelte'
	import Button from './elements/Button.svelte'
	import Breadcrumbs from './elements/Breadcrumbs.svelte'
	import PluginCollection from './elements/PluginCollection.svelte'
	import { clearNav } from './pagedata.svelte'

	let err

	$: loadPersonDetails($params.username)
	let personDetails = null

	function loadPersonDetails(githubUsername) {
		clearNav()
		const done = wait()
		getPersonDetails(githubUsername)
			.then(details => personDetails = details)
			.catch(e => err = e)
			.finally(() => done())
	}

	function viewOnGitHub() {
		openURL(`https://github.com/${personDetails.person.githubUsername}`)
			.catch(e => err = e)
	}

</script>

<Error err={err} />

<Breadcrumbs>
	<span>
		Contributors
	</span>
	<span>﹥</span>
	<strong>
		<code>@{personDetails ? personDetails.person.githubUsername : ''}</code>
	</strong>
</Breadcrumbs>

{#if personDetails}
	<div class='flex space-x-4 max-w-2xl p-6'>
		<div>
			<img 
				alt='Profile pic for {personDetails.person.name}'
				src='{personDetails.person.imageURL}' 
			/>
		</div>
		<div>
			<h1 class='text-lg'>
				{#if personDetails.person.githubUsername != personDetails.person.name}
					<strong>{personDetails.person.name}</strong>
				{/if}
				<span class='text-gray-500 dark:text-gray-400'>
					@{personDetails.person.githubUsername}
				</span>
			</h1>
			{#if personDetails.person.bio}
				<p>
					{personDetails.person.bio}
				</p>
			{/if}
			<p class='mt-3'>
				<Button on:click={ viewOnGitHub }>View on GitHub</Button>
			</p>
		</div>
	</div>
	<div>
		<PluginCollection plugins={personDetails.plugins} />
	</div>
{/if}


================================================
FILE: app/frontend/src/PluginView.svelte
================================================
<script>

	import { params } from 'svelte-hash-router'
	import { installedPlugins } from './pagedata.svelte'
	import { getPlugin, installPlugin, refreshInstalledPlugins } from './rpc.svelte'
	import { wait } from './waiters.svelte'
	import Error from './elements/Error.svelte'
	import Button from './elements/Button.svelte'
	import Breadcrumbs from './elements/Breadcrumbs.svelte'
	import PluginDetails from './elements/PluginDetails.svelte'
	import PluginSourceBrowser from './elements/PluginSourceBrowser.svelte'

	$: loadPlugin($params._)
	let err

	let plugin = null

	function loadPlugin(pluginPath) {
		if (pluginPath == '') { return}
		const done = wait()
		getPlugin(pluginPath)
			.then(p => plugin = p)
			.catch(e => err = e)
			.finally(() => done())
	}

	function install(plugin) {
		const done = wait()
		let installedPluginPath = ""
		installPlugin(plugin)
			.then(path => {
				installedPluginPath = path
				return refreshInstalledPlugins(installedPlugins)
			})
			.then(() => {
				if (installedPluginPath === '') { return }
				selectInstalledPlugin(installedPluginPath)
			})
			.finally(() => done())
	}

</script>

<style>
	.plugin-image {
		max-width: 300px;
	}

	.min-height-m-c {
		min-height: min-content;
	}
</style>

<Error err={err} />

<Breadcrumbs categoryPathSegments={plugin ? plugin.categoryPathSegments : null}>
	<strong>{plugin ? plugin.filename : ''}</strong>
</Breadcrumbs>

{#if plugin}
	<div class='flex flex-col h-full max-w-full'>
		<div class='p-6 flex flex-wrap space-x-8 min-height-m-c'>
			<div>
				<PluginDetails plugin={plugin} />
				<p class='p-3'>
					<Button
						style='primary'
						on:click={() => install(plugin)}
					>
						Install
					</Button>
				</p>
			</div>
			{#if plugin.imageURL}
				<div class='flex-shrink mb-8'>
					<img 
						alt='Screenshot of {plugin.title}' 
						src={plugin.imageURL} 
						onerror='this.style.display="none"'
						class='plugin-image max-h-64'
					/>
				</div>
			{/if}
		</div>
		<div class='flex-grow bg-white/75 dark:bg-gray-700/75 p-3 border-t border-gray-200 dark:border-gray-900 '>
			<PluginSourceBrowser files={plugin.files} />
		</div>
	</div>
{/if}


================================================
FILE: app/frontend/src/PluginsList.svelte
================================================
<script>

	import { params } from 'svelte-hash-router'
	import Error from './elements/Error.svelte'
	import Breadcrumbs from './elements/Breadcrumbs.svelte'
	import PluginCollection from './elements/PluginCollection.svelte'
	import { categories } from './pagedata.svelte'
	import { getCategoryByPath, getPlugins } from './rpc.svelte'
	import { wait } from './waiters.svelte'
	
	$: loadPlugins($categories, $params._)

	let category = null
	let plugins = null
	let err = ''

	function loadPlugins(categories, path) {
		if (categories == null) { return }
		category = getCategoryByPath(categories, path)
		const done = wait()
		plugins = null
		getPlugins(path)
			.then(response => {
				plugins = response
			})
			.catch(e => err = e)
			.finally(() => done())
	}

</script>

<Error err={err} />

<Breadcrumbs 
	hideLast={true}
	categoryPathSegments={category ? category.categoryPathSegments : null}
>
	<strong>{category ? category.text : ''}</strong>
</Breadcrumbs>

<div class='pt-6'>
	<PluginCollection plugins={plugins} />
</div>


================================================
FILE: app/frontend/src/backend/index.js
================================================
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT

const backend = {
  "main": {
    "CategoriesService": {
      /**
       * GetCategories
       * @returns {Promise<Array.<any>|Error>}  - Go Type: []main.Category
       */
      "GetCategories": () => {
        return window.backend.main.CategoriesService.GetCategories();
      },
    }
    "CommandService": {
      /**
       * ClearCache
       * @returns {Promise<void>} 
       */
      "ClearCache": () => {
        return window.backend.main.CommandService.ClearCache();
      },
      /**
       * OpenFile
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<Error>}  - Go Type: error
       */
      "OpenFile": (arg1) => {
        return window.backend.main.CommandService.OpenFile(arg1);
      },
      /**
       * OpenPath
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<Error>}  - Go Type: error
       */
      "OpenPath": (arg1) => {
        return window.backend.main.CommandService.OpenPath(arg1);
      },
      /**
       * OpenURL
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<Error>}  - Go Type: error
       */
      "OpenURL": (arg1) => {
        return window.backend.main.CommandService.OpenURL(arg1);
      },
      /**
       * RefreshAllPlugins
       * @returns {Promise<void>} 
       */
      "RefreshAllPlugins": () => {
        return window.backend.main.CommandService.RefreshAllPlugins();
      },
      /**
       * WindowHide
       * @returns {Promise<void>} 
       */
      "WindowHide": () => {
        return window.backend.main.CommandService.WindowHide();
      },
      /**
       * WindowMinimise
       * @returns {Promise<void>} 
       */
      "WindowMinimise": () => {
        return window.backend.main.CommandService.WindowMinimise();
      },
    }
    "PersonService": {
      /**
       * GetPersonDetails
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<any|Error>}  - Go Type: *main.PersonDetails
       */
      "GetPersonDetails": (arg1) => {
        return window.backend.main.PersonService.GetPersonDetails(arg1);
      },
    }
    "PluginsService": {
      /**
       * GetFeaturedPlugins
       * @returns {Promise<Array.<any>|Error>}  - Go Type: []metadata.Plugin
       */
      "GetFeaturedPlugins": () => {
        return window.backend.main.PluginsService.GetFeaturedPlugins();
      },
      /**
       * GetInstalledPluginMetadata
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<any|Error>}  - Go Type: *main.InstalledPluginMetadata
       */
      "GetInstalledPluginMetadata": (arg1) => {
        return window.backend.main.PluginsService.GetInstalledPluginMetadata(arg1);
      },
      /**
       * GetInstalledPlugins
       * @returns {Promise<Array.<any>|Error>}  - Go Type: []plugins.InstalledPlugin
       */
      "GetInstalledPlugins": () => {
        return window.backend.main.PluginsService.GetInstalledPlugins();
      },
      /**
       * GetPlugin
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<any|Error>}  - Go Type: *metadata.Plugin
       */
      "GetPlugin": (arg1) => {
        return window.backend.main.PluginsService.GetPlugin(arg1);
      },
      /**
       * GetPlugins
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<Array.<any>|Error>}  - Go Type: []metadata.Plugin
       */
      "GetPlugins": (arg1) => {
        return window.backend.main.PluginsService.GetPlugins(arg1);
      },
      /**
       * InstallPlugin
       * @param {any} arg1 - Go Type: metadata.Plugin
       * @returns {Promise<string|Error>}  - Go Type: string
       */
      "InstallPlugin": (arg1) => {
        return window.backend.main.PluginsService.InstallPlugin(arg1);
      },
      /**
       * LoadVariableValues
       * @param {string} arg1 - Go Type: string
       * @returns {Promise<any|Error>}  - Go Type: map[string]interface {}
       */
      "LoadVariableValues": (arg1) => {
        return window.backend.main.PluginsService.LoadVariableValues(arg1);
      },
      /**
       * SaveVariableValues
       * @param {string} arg1 - Go Type: string
       * @param {any} arg2 - Go Type: map[string]interface {}
       * @returns {Promise<Error>}  - Go Type: error
       */
      "SaveVariableValues": (arg1, arg2) => {
        return window.backend.main.PluginsService.SaveVariableValues(arg1, arg2);
      },
      /**
       * SetEnabled
       * @param {string} arg1 - Go Type: string
       * @param {boolean} arg2 - Go Type: bool
       * @returns {Promise<string|Error>}  - Go Type: string
       */
      "SetEnabled": (arg1, arg2) => {
        return window.backend.main.PluginsService.SetEnabled(arg1, arg2);
      },
      /**
       * SetRefreshInterval
       * @param {string} arg1 - Go Type: string
       * @param {any} arg2 - Go Type: plugins.RefreshInterval
       * @returns {Promise<any|Error>}  - Go Type: *main.SetRefreshIntervalResult
       */
      "SetRefreshInterval": (arg1, arg2) => {
        return window.backend.main.PluginsService.SetRefreshInterval(arg1, arg2);
      },
      /**
       * UninstallPlugin
       * @param {any} arg1 - Go Type: main.UninstallPluginRequest
       * @returns {Promise<boolean|Error>}  - Go Type: bool
       */
      "UninstallPlugin": (arg1) => {
        return window.backend.main.PluginsService.UninstallPlugin(arg1);
      },
    }
  }

};
export default backend;


================================================
FILE: app/frontend/src/backend/package.json
================================================
{
  "name": "backend",
  "version": "1.0.0",
  "description": "Package to wrap backend method calls",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

================================================
FILE: app/frontend/src/elements/A.svelte
================================================
<script>
    export let href = ''
    export let underline = false
    export let cssclass = ''
</script>
<a 
    on:click
    {href}
    class='hover:underline {cssclass}'
    class:underline={underline}
><slot /></a>


================================================
FILE: app/frontend/src/elements/Breadcrumbs.svelte
================================================
<script>
    
    import A from './A.svelte'

    // categoryPathSegments are the segments to show in the
    // breadcrumbs.
    export let categoryPathSegments = null

    // hideLast will hide the last segment.
    // Used to avoid repeating it when it is represented
    // in the slot.
    export let hideLast = false

    $: segments = (hideLast && categoryPathSegments) ? categoryPathSegments.filter(seg => !seg.isLast) : categoryPathSegments

</script>

<style>
    .breadcrumbs {
        background: rgb(243,244,246);
        background: linear-gradient(90deg, rgba(243,244,246,0.75) 0%, rgba(243,244,246,0) 100%);
    }
    @media (prefers-color-scheme: dark) {
        .breadcrumbs {
            background: rgb(55,65,81);
            background: linear-gradient(90deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0) 100%);
        }
        input:focus, select:focus {
            outline: rgba(0,0,0,0.5) auto 1px;
            outline-color: rgba(0,0,0,0.5);
            outline-style: auto;
            outline-width: 2px;
        }
    }
</style>

<div>
    <div class='w-full'>
        <div class='breadcrumbs flex space-x-1 text-sm px-3 py-1'>
            <A href='#/'>Home</A>
            <span>﹥</span>
            {#if segments}
                {#each segments as categoryPathSegment}
                    <A href='#/plugins/{categoryPathSegment.path}'>
                        {categoryPathSegment.text}
                    </A>
                    <span>﹥</span>
                {/each}
            {/if}
            <span>
                <slot />
                &nbsp;
            </span>
        </div>
    </div>
</div>


================================================
FILE: app/frontend/src/elements/Button.svelte
================================================
<script>

	import Spinner from './Spinner.svelte'

	// style is the style of button.
	// default or primary, see classes below.
	export let style = 'default'

	// cssclass appends per-element css classes.
	export let cssclass = ''

	// waiter will show a spinner inside the button
	// if > 0.
	export let waiter = 0

	// classes are the css classes to use for each style of button
	const classes = {
		'default': 'text-sm rounded shadow-sm dark:shadow-none px-4 py-1 border bg-gray-100 dark:text-gray-300 dark:bg-gray-900 border-gray-300 dark:border-gray-600 dark:bg-opacity-50 hover:bg-opacity-75',
		'primary': 'bg-blue-900 text-sm text-white rounded shadow-sm dark:shadow-none px-4 py-1 border border-blue-900 dark:bg-black dark:border-black dark:bg-opacity-50 hover:bg-opacity-75',
	}

	// css is the final css string to inject into the element.
	$: css = classes[style] + ' ' + cssclass

</script>
<button 
	on:click
	class='{css}'
>
	<div class:animate-spin={waiter}>
		<slot />
	</div>
</button>


================================================
FILE: app/frontend/src/elements/Duration.svelte
================================================
<script>
	import { createEventDispatcher } from 'svelte'
	const dispatch = createEventDispatcher()
    import Spinner from './Spinner.svelte'

    export let value = {
        n: 0,
        unit: 'seconds',
    }
    export let disabled = false

    let spinnerVisible = false
    let timeout
    function onChange() {
        spinnerVisible = true
        clearTimeout(timeout)
        timeout = setTimeout(() => {
            dispatch('change')
            spinnerVisible = false
        }, 300)
    }

</script>

<style>
    .val {
        width: 45px;
        text-align: right;
    }
</style>

<input 
    class='val border bg-gray-100 active:bg-gray-300 dark:text-gray-400 dark:bg-gray-700 border-gray-300 dark:border-gray-600 dark:bg-opacity-50' 
    type='number' 
    bind:value={value.n}
    on:change={onChange}
    disabled={disabled}
>
{#if value.unit == 'milliseconds'}
    ms
{:else}
    <select 
        class='mr-1 bg-gray-100 active:bg-gray-300 dark:text-gray-400 dark:bg-black border-gray-300 dark:border-gray-600'
        bind:value={value.unit}
        on:change={onChange}
        disabled={disabled}
    >
        <option>seconds</option>
        <option>minutes</option>
        <option>hours</option>
        <option>days</option>
    </select>
{/if}
<Spinner 
    visible={spinnerVisible} 
    width='16' height='16'
/>


================================================
FILE: app/frontend/src/elements/Error.svelte
================================================
<script>
	import { slide } from 'svelte/transition'
	import { openURL } from '../rpc.svelte'
	import { fireSigRefresh } from '../signals.svelte'
	import Button from './Button.svelte'
	export let err = null

	function reportError(err) {
		openURL('https://github.com/matryer/xbar/issues/new?title=' + encodeURIComponent(err))
	}

	function hideError() {
		err = null
	}

    // isNotFoundError returns true if the error is a 'not found'
    // error.
    export function isNotFoundError(err) {
        if (!err) {
            return false
        }
        const errStr = err.toString()
        if (errStr === '') {
            return false
        }
        return errStr.includes('no such file or directory')
    }

	let refreshing = false
	function onRefreshClicked() {
		refreshing = true
		window.setTimeout(() => {
			fireSigRefresh()
			refreshing = false
		}, 300)
	}

</script>
{#if err}
	{#if isNotFoundError(err)}
		<div class='px-8 py-4 bg-white bg-opacity-50 dark:bg-black/25'>
			<code>404</code> Not found
			<Button cssclass='ml-4' on:click={ onRefreshClicked }>
				{#if refreshing}Refreshing&hellip;{:else}Refresh{/if}
			</Button>
		</div>
		<p class='p-8 py-4'>
			← Use the navigation to 
			find your way back.
		</p>
	{:else}
		<div transition:slide class='flex px-8 py-4 bg-yellow-200 bg-opacity-50 dark:text-white'>
			<div class='flex-grow'>
				<strong>Something went wrong, this:</strong> {err}
				<a class='ml-4 underline' href='#/report-error' on:click|preventDefault='{ () => { reportError(err) } }'>Open issue</a>
			</div>
			<div>
				<a 
					href='/close'
					on:click|preventDefault={ hideError }
				>×</a>
			</div>
		</div>
	{/if}
{/if}


================================================
FILE: app/frontend/src/elements/KeyboardShortcuts.svelte
================================================
<script>
    
    import { fireSigRefresh } from '../signals.svelte'

    function handleKeydown(e) {
        if (e.key === 'r' && e.metaKey && !e.shiftKey) {
            e.preventDefault()
            fireSigRefresh()
            return
        }
        if (e.key === 'w' && e.metaKey && !e.shiftKey) {
            e.preventDefault()
            backend.main.CommandService.WindowHide()
            return
        }
        if (e.key === 'm' && e.metaKey && !e.shiftKey) {
            e.preventDefault()
            backend.main.CommandService.WindowMinimise()
            return
        }
    }
    
</script>
<svelte:window 
    on:keydown={handleKeydown}
/>


================================================
FILE: app/frontend/src/elements/PluginCollection.svelte
================================================
<script>
	import Button from './Button.svelte'
	import A from './A.svelte'
	import { installedPlugins, selectInstalledPlugin } from '../pagedata.svelte'
	import { installPlugin, refreshInstalledPlugins } from '../rpc.svelte'
	import { wait } from '../waiters.svelte'

	export let plugins = null

	function install(plugin) {
		const done = wait()
		let installedPluginPath = ""
		installPlugin(plugin)
			.then(path => {
				installedPluginPath = path
				return refreshInstalledPlugins(installedPlugins)
			})
			.then(() => {
				if (installedPluginPath === '') { return }
				selectInstalledPlugin(installedPluginPath)
			})
			.finally(() => done())
	}
    
	function gotoPerson(githubUsername) {
        location.hash = `/people/${githubUsername}`
    }

</script>

<style>

	.author-pic {
		width: 25px;
		height: 25px;
	}
	.plugin-pic {
		max-width: 64px;
		max-height: 64px;
	}
	.plugin-desc {
		max-height: 7em;
		white-space: normal;
		text-overflow: ellipsis;
		overflow: hidden;
	}

</style>

{#if plugins}
	<div class='flex flex-wrap p-3'>
		{#each plugins as plugin}
			<div
				class='flex flex-col bg-white/25 dark:bg-black/25 m-3 mt-0 mb-6 w-80 rounded shadow-lg dark:shadow-none'
			>
				<div class='flex-grow'>
					<h2 class='text-black flex-grow dark:text-white text-lg p-4 pb-0'>
						<a href='#/plugin-details/{plugin.path}'>
							{plugin.title}
						</a>
					</h2>
					<div class='flex'>
						{#if plugin.imageURL}
							<a href='#/plugin-details/{plugin.path}'>
								<img
									class='plugin-pic float-left m-4 mr-0'
									alt='Photo for {plugin.title}'
									src='{plugin.imageURL}'
									onerror='this.style.display="none"'
								>
							</a>
						{/if}
						<p class='plugin-desc mb-2 p-4 test-gray break-words'>
							{plugin.desc}
						</p>
					</div>
				</div>
				{#if plugin.authors && plugin.authors.length}
					<div class='flex items-center space-x-2 p-4'>
						<img 
							class='author-pic rounded-full'
							src='{plugin.authors[0].imageURL}'
							alt='Profile picture for {plugin.authors[0].name}'
							onerror='this.style.display="none"'
							on:click={ gotoPerson(plugin.authors[0].githubUsername) }
						/>
						<p 
							class='ml-2 overflow-hidden overflow-ellipsis flex-initial'
						>
							<A href='#/people/{plugin.authors[0].githubUsername}'>@{plugin.authors[0].githubUsername}</A>
						</p>
						<div class='flex-grow'></div>
						<div class='flex-shrink-0'>
							<Button
								style='primary'
								on:click={() => install(plugin)}
							>
								Install
							</Button>
						</div>
					</div>
				{/if}
			</div>
		{/each}
	</div>
{/if}


================================================
FILE: app/frontend/src/elements/PluginDetails.svelte
================================================
<script>

	/*
	
		Usage:

			<PluginDetails plugin={plugin}>
				<div slot='action'>
					Actions like switches etc
					Goes in the top right.
				</div>
				<div slot='footer'>
					Additional detials in the footer of the
					card.
				</div>
			</PluginDetails>

	*/

	import Button from './Button.svelte'

	export let plugin

	function gotoPerson(githubUsername) {
		location.hash = `/people/${githubUsername}`
	}

</script>

<style>
	.plugin-desc {
		min-width: 200px;
	}
</style>

{#if plugin}
	<div class='flex flex-col bg-white/25 dark:bg-black/25 mt-0 mb-6 rounded shadow-lg dark:shadow-none'>

		{#if $$slots.action}
			<div class='pt-4 px-4 flex'>
				<h1 class='flex-grow font-bold text-lg'>
					{plugin.title || plugin.filename}
				</h1>
				<slot name='action' />
			</div>
		{:else}
			<h1 class='p-4 pb-1 font-bold text-lg'>
				{plugin.title || plugin.filename}
			</h1>
		{/if}

		<p class='px-4 text-gray-500 dark:text-gray-400'>
			by 
			{#if plugin.authors}
				{#each plugin.authors as author}
					<span class='mr-2'>
						{author.name || author.githubUsername}
						{#if author.githubUsername}
							<Button on:click={() => gotoPerson(author.githubUsername)}>
								@{author.githubUsername}
							</Button>
						{/if}
					</span>
				{/each}
			{:else}
				Anon.
			{/if}
		</p>
		<div class='flex-grow p-4 plugin-desc break-word max-w-md mt-2 text-lg overflow-y-scroll overflow-x-hidden'>
			<p>
				{plugin.desc}
			</p>
		</div>
		{#if $$slots.footer}
			<slot name='footer' />
		{/if}
	</div>
{/if}


================================================
FILE: app/frontend/src/elements/PluginSourceBrowser.svelte
================================================
<script>

    import { createEventDispatcher } from 'svelte'
    import Button from './Button.svelte'

    const dispatch = createEventDispatcher()
    
    export let showEditButton = false
    export let files

    function editFile(file) {
        dispatch('openEditor', {file})
    }
</script>
{#if files && files.length > 0}
    {#each files as file}
        <div class='flex'>
            <h2 class='flex-grow uppercase text-sm text-gray-500 mb-4'>
                Source code
            </h2>
            {#if showEditButton}
                <div class='pr-3'>
                    <Button
                        on:click={ () => editFile(file) }
                    >Open in external editor</Button>
                </div>
            {/if}
        </div>
        <pre class='whitespace-pre-wrap nice-wrapping text-sm pb-8'><code>{file.content}</code></pre>
    {/each}
{/if}


================================================
FILE: app/frontend/src/elements/Spinner.svelte
================================================
<script>
    export let waiter = 0
    export let visible = false
    
    $: waiterUpdated(waiter)

    export let width = 24
    export let height = 24

    let timeout = null
    function waiterUpdated(v) {
        if (v == 0) {
            clearTimeout(timeout)
            visible = false
        }
        if (v == 1) {
            timeout = setTimeout(() => {
                if (waiter > 0) {
                    visible = true
                }
            }, 300)
        }
    }

</script>
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg class='inline-block' class:invisible='{!visible}' width="{width}" height="{height}" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <linearGradient x1="8.042%" y1="0%" x2="65.682%" y2="23.865%" id="a">
            <stop stop-color="#888" stop-opacity="0" offset="0%"/>
            <stop stop-color="#888" stop-opacity=".631" offset="63.146%"/>
            <stop stop-color="#888" offset="100%"/>
        </linearGradient>
    </defs>
    <g fill="none" fill-rule="evenodd">
        <g transform="translate(1 1)">
            <path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke="url(#a)" stroke-width="3">
                <animateTransform
                    attributeName="transform"
                    type="rotate"
                    from="0 18 18"
                    to="360 18 18"
                    dur="0.7s"
                    repeatCount="indefinite" />
            </path>
            <circle fill="#888" cx="36" cy="18" r="1">
                <animateTransform
                    attributeName="transform"
                    type="rotate"
                    from="0 18 18"
                    to="360 18 18"
                    dur="0.7s"
                    repeatCount="indefinite" />
            </circle>
        </g>
    </g>
</svg>

================================================
FILE: app/frontend/src/elements/Switch.svelte
================================================
<script>

	import { onMount, createEventDispatcher } from 'svelte'
	const dispatch = createEventDispatcher()

	export let on = false
	export let loading = false
	export let theme = 'blue'

	function click() {
		if (loading) { return }
		dispatch('click')
	}

	let animate = false
	onMount(() => {
		// start animating after a short period
		window.setTimeout(() => animate = true, 300)
	})
	
</script>

<style>
	.switch-container {
		display: inline-block;
		padding: 5px;
		vertical-align: middle;
		overflow: hidden;
	}
	.switch {
		display: flex;
		align-items: center;
		width: 32px;
		height: 18px;
		border-radius: 20px;
		background-color: #bbb;
		padding: 2px 4px;

		-moz-user-select: none;
		-khtml-user-select: none;
		-webkit-user-select: none;
		-ms-user-select: none;
		user-select: none;
	}
	.switch:hover {
		background-color: #aaa;
	}
	.switch .toggle {
		display: flex;
		align-items: center;
		justify-content: center;
		background-color: white;
		width: 12px;
		height: 12px;

		border-radius: 50%;
		line-height: 0.75em;
	}
	.switch.on {
	}
	.switch.on:hover {
		background-color: #43649f;
	}
	.switch.on .toggle {
		margin-left: 13px;
	}
	.disabled .toggle {
		background-color: transparent;
	}
	.animate {
		transition: 0.25s;
		transition-property: margin-left;
	}
	.switch.on.theme-blue {
		background-color: #4286d6;
	}
	.switch.on.theme-blue:hover {
		background-color: #43649f;
	}
	.switch.on.theme-bright {
		background-color: rgb(84, 146, 99);
	}
	.switch.on.theme-bright:hover {
		background-color: rgb(67, 117, 79);
	}
</style>

<a 
	class='switch-container'
	on:click|preventDefault|stopPropagation={click}
>
	<span 
		class='switch theme-{theme}'
		class:on={on}
	>
		<span 
			class='toggle'
			class:animate={animate}
			class:disabled={loading}
		>
		</span>
	</span>
</a>


================================================
FILE: app/frontend/src/elements/VariableInput.svelte
================================================
<script>

	import { createEventDispatcher } from 'svelte'

    // dispatch handles the following events:
    // 		on:change - fired when the values change
    const dispatch = createEventDispatcher()

    // variable is the variable to represent.
    export let variable

    // values is a map of values which will be
    // set by the inputs.
    export let values = {}

    // disabled is whether this input is disabled or not.
    export let disabled = false

    // fire on change whenever values change
    $: valueDidChange(values)
	function valueDidChange(values) {
        dispatch('change')
	}

</script>

{#if variable}
    {#if variable.type === 'string'}
        <input 
            id='{variable.name}'
            class='border px-2 bg-gray-100 active:bg-gray-300 dark:text-gray-400 dark:bg-black/50 border-gray-300 dark:border-gray-600'
            type='text' 
            bind:value='{ values[variable.name] }'
            disabled={disabled}
        />
    {:else if variable.type === 'boolean'}
        <label class='flex items-center'>
            <input 
                id='{variable.name}'
                class='border px-2 bg-gray-100 active:bg-gray-300 dark:text-gray-400 dark:bg-black/50 border-gray-300 dark:border-gray-600'
                type='checkbox' 
                bind:checked='{ values[variable.name] }'
                disabled={disabled}
            />
            {#if values[variable.name]}
                <code class='ml-3'>TRUE</code>
            {:else}
                <code class='ml-3'>FALSE</code>
            {/if}
        </label>
    {:else if variable.type === 'number'}
        <input 
            id='{variable.name}'
            class='border px-2 bg-gray-100 active:bg-gray-300 dark:text-gray-400 dark:bg-black/50 border-gray-300 dark:border-gray-600'
            type='number' 
            bind:value='{ values[variable.name] }'
            disabled={disabled}
        />
    {:else if variable.type === 'select'}
        <select 
            id='{variable.name}'
            class='border px-2 bg-gray-100 active:bg-gray-300 dark:text-gray-400 dark:bg-black border-gray-300 dark:border-gray-600'
            bind:value='{ values[variable.name] }'
            disabled={disabled}
        >
            {#each variable.options as option}
                <option 
                    value='{option}'
                >{option}</option>
            {/each}
        </select>
    {/if}
{/if}


================================================
FILE: app/frontend/src/elements/Variables.svelte
================================================
<script>
	import { createEventDispatcher } from 'svelte'
    import { refreshAllPlugins } from '../rpc.svelte'

    // dispatch handles the following events:
    // 		on:change - fired when the values change
    const dispatch = createEventDispatcher()

    import VariableInput from './VariableInput.svelte'

    export let variables = null
    export let values
    export let disabled = false

    let onChangeDebounceTimeout
    function onChange() {
        clearTimeout(onChangeDebounceTimeout)
        onChangeDebounceTimeout = setTimeout(fireOnChangeEvent, 300)
    }

    function fireOnChangeEvent() {
        dispatch('change')
    }

    function refresh() {
        dispatch('change')
        setTimeout(refreshAllPlugins, 100)
    }

</script>

{#if variables && variables.length && values}
    <div class='p-6 pb-0 bg-white/25 dark:bg-gray-700/50 border-t border-gray-100 dark:border-gray-600'>
        <table class='table-auto'>
            {#each variables as variable}
                <tr>
                    <td class='py-3 pr-6 text-sm'>
						<label for='{variable.name}'>
							<code>
								{variable.label}
							</code>
						</label>
                    </td>
                    <td class='py-3 pr-6'>
						<VariableInput 
                            on:change={onChange}
							values={values}
							variable={variable}
                            disabled={disabled}
						/>
                    </td>
                    <td class='py-3 pr-6 max-w-md text-sm text-gray-500 dark:text-gray-400'>
						{variable.desc}
                    </td>
                </tr>
            {/each}
        </table>
        <p class='pt-3 pb-6 opacity-75 text-sm'>
            <span class='bg-yellow-100 bg-opacity-50 dark:bg-transparent'>💡 You must <a href='#/refresh-all' class='underline' on:click|preventDefault={refresh}>refresh the plugin</a> for changes to take effect.</span>
        </p>
    </div>
{/if}


================================================
FILE: app/frontend/src/main.js
================================================
import App from './App.svelte'
import { ready } from '@wails/runtime'
import { routes } from 'svelte-hash-router'
import Homepage from './Homepage.svelte'
import PluginsList from './PluginsList.svelte'
import PluginView from './PluginView.svelte'
import PeopleView from './PersonView.svelte'
import InstalledPluginView from './InstalledPluginView.svelte'

let app;

routes.set({
	'/': Homepage,
	'/plugins/*': PluginsList,
	'/installed-plugins/*': InstalledPluginView,
	'/plugin-details/*': PluginView,
	'/people/:username': PeopleView,
})

ready(() => {
	app = new App({
		target: document.body,
	});
});

export default app;


================================================
FILE: app/frontend/src/pagedata.svelte
================================================
<script context='module'>

    import { writable } from 'svelte/store'

    export const categories = writable(null)
    export const installedPlugins = writable(null)

    export const selectedCategoryPath = writable(null)
    export const selectedInstalledPluginPath = writable(null)

    export function selectCategory(categoryPath) {
        selectedInstalledPluginPath.set(null)
        selectedCategoryPath.set(categoryPath)
        location.hash = `/plugins/${categoryPath}`
    }

    export function selectInstalledPlugin(installedPluginPath) {
        selectedCategoryPath.set(null)
        selectedInstalledPluginPath.set(installedPluginPath)
        location.hash = `/installed-plugins/${installedPluginPath}`
    }

    export function clearNav() {
        selectedCategoryPath.set(null)
        selectedInstalledPluginPath.set(null)
    }

</script>


================================================
FILE: app/frontend/src/rpc.svelte
================================================
<script context='module'>

	export function openURL(url) {
		return backend.main.CommandService.OpenURL(url)
	}

	export function openFile(path) {
		return backend.main.CommandService.OpenFile(path)
	}

	export function refreshCategories(categories) {
		return backend.main.CategoriesService.GetCategories()
			.then(result => categories.set(result))
	}

	export function getPlugins(categoryPath) {
		return backend.main.PluginsService.GetPlugins(categoryPath)
	}

	export function getPlugin(pluginPath) {
		return backend.main.PluginsService.GetPlugin(pluginPath)
	}

	export function getFeaturedPlugins() {
		return backend.main.PluginsService.GetFeaturedPlugins()
	}

	export function getPersonDetails(githubUsername) {
		return backend.main.PersonService.GetPersonDetails(githubUsername)
	}

	export function installPlugin(plugin) {
		const pluginInfo = {
			title: plugin.title,
			path: plugin.path,
		}
		return backend.main.PluginsService.InstallPlugin(pluginInfo)
	}

	export function uninstallPlugin(plugin) {
		const pluginInfo = {
			title: plugin.title,
			path: plugin.path,
		}
		return backend.main.PluginsService.UninstallPlugin(pluginInfo)
	}

	export function refreshInstalledPlugins(installedPlugins) {
		return backend.main.PluginsService.GetInstalledPlugins()
			.then(result => installedPlugins.set(result))
	}

	export function getInstalledPluginMetadata(installedPluginPath) {
		return backend.main.PluginsService.GetInstalledPluginMetadata(installedPluginPath)
	}

	export function loadVariableValues(installedPluginPath) {
		return backend.main.PluginsService.LoadVariableValues(installedPluginPath)
	}
	
	export function saveVariableValues(installedPluginPath, values) {
		return backend.main.PluginsService.SaveVariableValues(installedPluginPath, values)
	}

	export function setEnabled(installedPluginPath, enabled) {
		return backend.main.PluginsService.SetEnabled(installedPluginPath, enabled)
	}

	export function setRefreshInterval(installedPluginPath, refreshInterval) {
		return backend.main.PluginsService.SetRefreshInterval(installedPluginPath, refreshInterval)
	}

	export function refreshAllPlugins() {
		return backend.main.CommandService.RefreshAllPlugins()
	}

	export function clearCache() {
		return backend.main.CommandService.ClearCache()
	}

	// getCategoryByPath gets a cateogry by path.
	// Pass in categories, imported from this file.
	// getCategoryByPath($categories, categoryPath)
	export function getCategoryByPath(categories, categoryPath) {
		for (let i = 0; i < categories.length; i++) {
			if (categories[i].path === categoryPath) {
				return categories[i]
			}
			const foundInChildren = getCategoryByPath(categories[i].children, categoryPath)
			if (foundInChildren) {
				return foundInChildren
			}
		}
		return null // not found
	}

</script>


================================================
FILE: app/frontend/src/signals.svelte
================================================
<script context='module'>

    import { writable } from "svelte/store"

    export const sigRefresh = writable(new Date())

    export function fireSigRefresh() {
        sigRefresh.set(new Date())
    }

</script>


================================================
FILE: app/frontend/src/styles.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

* {
	cursor: default !important;
}

html {
	font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
	-webkit-font-smoothing: subpixel-antialiased;
	font-size: 13px;
}

@media (prefers-color-scheme: dark) {
	html {
		background-color: rgb(24,24,28,0.5);
	}
}

html, body {
	height: 100%;
	width: 100%;
	position: fixed;
	overflow: hidden;
	overscroll-behavior-y: none;
	
}

/* from https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ */
.nice-wrapping {
	/* These are technically the same, but use both */
	overflow-wrap: break-word;
	word-wrap: break-word;

	-ms-word-break: break-all;
	/* This is the dangerous one in WebKit, as it breaks things wherever */
	word-break: break-all;
	/* Instead use this non-standard one: */
	word-break: break-word;

	/* Adds a hyphen where the word breaks, if supported (No Blink) */
	-ms-hyphens: auto;
	-moz-hyphens: auto;
	-webkit-hyphens: auto;
	hyphens: auto;
}

::-webkit-scrollbar-track
{
	background: transparent;
	border-radius: 2px;
	padding: 2px;
	padding-right: 10px;
}

::-webkit-scrollbar
{
	width: 12px;
	width: 6px;
	background: transparent;
	margin-right: 50px;
}

::-webkit-scrollbar-thumb
{
	border-radius: 2px;
	width: 6px;
	background: rgba(160,160,160,0.5);
}

/* Credit: https://stackoverflow.com/a/4407335 */
.noselect {
	cursor: default;
	-webkit-touch-callout: none; /* iOS Safari */
	-webkit-user-select: none; /* Safari */
	-khtml-user-select: none; /* Konqueror HTML */
	-moz-user-select: none; /* Old versions of Firefox */
	-ms-user-select: none; /* Internet Explorer/Edge */
	user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */
}
.allow-select {
	-webkit-touch-callout: initial; /* iOS Safari */
	-webkit-user-select: initial; /* Safari */
	-khtml-user-select: initial; /* Konqueror HTML */
	-moz-user-select: initial; /* Old versions of Firefox */
	-ms-user-select: initial; /* Internet Explorer/Edge */
	user-select: initial; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */
}


================================================
FILE: app/frontend/src/waiters.svelte
================================================
<script context='module'>

    /*
    
        The globalWaiter drives the app spinner.

        Whenever you're going to perform a blocking network operation,
        like loading data, call wait(). wait() returns a done function,
        which you should call when it's finished.

        const done = wait()
        operation()
            .then(() => {})
            .catch(() => {})
            .finally(() => done())

        The Spinner is driven by this value, and includes a slight delay
        in case the operation completes within a short period.

    */

    import { writable } from "svelte/store"

    // globalWaiter is a counter used to keep track
    // of network activity.
    export const globalWaiter = writable(0)

    export function wait() {
        globalWaiter.update(v => v + 1)
        return function() {
            globalWaiter.update(v => v - 1)
        }
    }

</script>


================================================
FILE: app/frontend/tailwind.config.js
================================================

const colors = require('tailwindcss/colors')

module.exports = {
	purge: [
		'./src/**/*.html',
		'./src/**/*.js',
		'./src/**/*.svelte',
	],
	theme: {
		colors: {
			transparent: 'transparent',

			gray: colors.neutral,
			blue: colors.blue,
			black: colors.black,
			white: colors.white,
			yellow: colors.yellow,
		},
	},
	plugins: [],
}


================================================
FILE: app/go.mod
================================================
module github.com/matryer/xbar/app

go 1.16

require (
	github.com/go-ole/go-ole v1.2.5 // indirect
	github.com/google/btree v1.0.1 // indirect
	github.com/google/uuid v1.2.0 // indirect
	github.com/gorilla/websocket v1.4.2 // indirect
	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
	github.com/klauspost/compress v1.13.1 // indirect
	github.com/leaanthony/go-ansi-parser v1.3.0
	github.com/matryer/is v1.4.0
	github.com/matryer/xbar/pkg/metadata v0.0.0-20210701102621-61a690f92a94
	github.com/matryer/xbar/pkg/plugins v0.0.0-20210701102621-61a690f92a94
	github.com/matryer/xbar/pkg/update v0.0.0-20210701102621-61a690f92a94
	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
	github.com/pierrec/lz4 v2.6.1+incompatible // indirect
	github.com/pkg/errors v0.9.1
	github.com/wailsapp/wails/v2 v2.0.0-alpha.74
	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
	nhooyr.io/websocket v1.8.7 // indirect
)

replace github.com/matryer/xbar/pkg/plugins => ../pkg/plugins

replace github.com/matryer/xbar/pkg/metadata => ../pkg/metadata

replace github.com/matryer/xbar/pkg/update => ../pkg/update

//replace github.com/wailsapp/wails/v2 => ../../wails/v2


================================================
FILE: app/go.sum
================================================
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ=
github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/debme v1.1.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
github.com/leaanthony/go-ansi-parser v1.2.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/go-ansi-parser v1.3.0 h1:8CRGdyxf2/cCiBZmYqpFkeTGcsFswDcVeB2SZKYUnbo=
github.com/leaanthony/go-ansi-parser v1.3.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/go-common-file-dialog v1.0.3 h1:O0uGjKnWtdEADGrkg+TyAAbZylykMwwx/MNEXn9fp+Y=
github.com/leaanthony/go-common-file-dialog v1.0.3/go.mod h1:TGhEc9eSJgRsupZ+iH1ZgAOnEo9zp05cRH2j08RPrF0=
github.com/leaanthony/gosod v1.0.1/go.mod h1:W8RyeSFBXu7RpIxPGEJfW4moSyGGEjlJMLV25wEbAdU=
github.com/leaanthony/idgen v1.0.0 h1:IZreR+JGEzFV4yeVuBZA25gM0keUoFy+RDUldncQ+Jw=
github.com/leaanthony/idgen v1.0.0/go.mod h1:4nBZnt8ml/f/ic/EVQuLxuj817RccT2fyrUaZFxrcVA=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/webview2runtime v1.1.0 h1:N0pv55ift8XtqozIp4PNOtRCJ/Qdd/qzx80lUpalS4c=
github.com/leaanthony/webview2runtime v1.1.0/go.mod h1:hH9GnWCve3DYzNaPOcPbhHQ7fodXR1QJNsnwixid4Tk=
github.com/leaanthony/winicon v0.0.0-20200606125418-4419cea822a0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.7/go.mod h1:w/yG+ezBeTdUxiKs5NcPicO9diP38nk96QBAbIIGeFs=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/wailsapp/wails/v2 v2.0.0-alpha.74 h1:h7K/gM2uR8HJQwb+VZYSTsbMqPz1O2Dx6jn9FFBA20o=
github.com/wailsapp/wails/v2 v2.0.0-alpha.74/go.mod h1:BFBtJpsnU9bMYfqhotj5wYn5EIGBXXJl5x+2ZT9UrKw=
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/ztrue/tracerr v0.3.0 h1:lDi6EgEYhPYPnKcjsYzmWw4EkFEoA/gfe+I9Y5f+h6Y=
github.com/ztrue/tracerr v0.3.0/go.mod h1:qEalzze4VN9O8tnhBXScfCrmoJo10o8TN5ciKjm6Mww=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=


================================================
FILE: app/incoming_urls.go
================================================
package main

import (
	"net/url"
	"strings"

	"github.com/pkg/errors"
)

type incomingURL struct {
	// Action is the action to take.
	Action string
	// Params are the parameters for the action.
	Params url.Values
}

// parseIncomingURL parses an incoming xbar:// URL.
func parseIncomingURL(urlStr string) (incomingURL, error) {
	var inURL incomingURL
	u, err := url.Parse(urlStr)
	if err != nil {
		return inURL, err
	}
	if u.Scheme != "xbar" && u.Host != "app.xbarapp.com" {
		return inURL, errors.New("not an xbar:// url")
	}
	inURL.Action = strings.Trim(u.Path, "/")
	inURL.Params = u.Query()
	switch inURL.Action {
	case "openPlugin":
	case "refreshPlugin":
	case "refreshAllPlugins":
	default: // not ok
		return inURL, errors.Errorf("unsupported action %q", inURL.Action)
	}
	return inURL, nil
}


================================================
FILE: app/incoming_urls_test.go
================================================
package main

import (
	"testing"

	"github.com/matryer/is"
)

func TestParseIncomingURL(t *testing.T) {
	is := is.New(t)

	result, err := parseIncomingURL(`xbar://app.xbarapp.com/openPlugin?path=IoT/homebridge.10s.py`)
	is.NoErr(err)
	is.Equal(result.Action, "openPlugin")
	is.Equal(result.Params.Get("path"), "IoT/homebridge.10s.py")

	result, err = parseIncomingURL(`xbar://app.xbarapp.com/refreshPlugin?path=cycle_text_and_detail`)
	is.NoErr(err)
	is.Equal(result.Action, "refreshPlugin")
	is.Equal(result.Params.Get("path"), "cycle_text_and_detail")

	result, err = parseIncomingURL(`xbar://app.xbarapp.com/refreshAllPlugins`)
	is.NoErr(err)
	is.Equal(result.Action, "refreshAllPlugins")

	result, err = parseIncomingURL(`xbar://app.xbarapp.com/nope?path=cycle_text_and_detail`)
	is.True(err != nil)

}


================================================
FILE: app/main.go
================================================
package main

import (
	_ "embed"
	"fmt"
	"os"

	"github.com/pkg/errors"
	"github.com/wailsapp/wails/v2"
	"github.com/wailsapp/wails/v2/pkg/logger"
	"github.com/wailsapp/wails/v2/pkg/options"
	"github.com/wailsapp/wails/v2/pkg/options/mac"
)

//go:embed .version
var version string

func main() {
	println("xbar", version)
	if err := run(); err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}
	println("xbar exited")
}

func run() error {
	app, err := newApp()
	if err != nil {
		return errors.Wrap(err, "newApp")
	}
	wailsLogLevel := logger.ERROR
	app.Verbose = true
	if app.Verbose {
		wailsLogLevel = logger.DEBUG
	}
	err = wails.Run(&options.App{
		Title:             "xbar",
		Width:             1080,
		Height:            700,
		MinWidth:          800,
		MinHeight:         600,
		StartHidden:       true,
		HideWindowOnClose: true,
		Mac: &mac.Options{
			WebviewIsTransparent:          true,
			WindowBackgroundIsTranslucent: true,
			TitleBar:                      mac.TitleBarHiddenInset(),
			Menu:                          app.appMenu,
			ActivationPolicy:              mac.NSApplicationActivationPolicyAccessory,
			URLHandlers: map[string]func(string){
				// xbar://...
				"xbar": app.handleIncomingURL,
			},
		},
		ContextMenus: app.contextMenus,
		LogLevel:     wailsLogLevel,
		Startup:      app.Start,
		Shutdown:     app.Shutdown,
		Bind: []interface{}{
			app.PersonService,
			app.CategoriesService,
			app.PluginsService,
			app.CommandService,
		},
	})
	if err != nil {
		return err
	}
	return nil
}


================================================
FILE: app/menu_parser.go
================================================
package main

import (
	"context"
	"fmt"
	"strconv"

	"github.com/leaanthony/go-ansi-parser"
	"github.com/matryer/xbar/pkg/plugins"
	"github.com/wailsapp/wails/v2/pkg/menu"
	"github.com/wailsapp/wails/v2/pkg/menu/keys"
)

// MenuParser translates xbar items into Wails menu items.
type MenuParser struct{}

// NewMenuParser makes a new MenuParser.
func NewMenuParser() *MenuParser {
	return &MenuParser{}
}

// ParseItems parses the items, returning the new menu.
func (m MenuParser) ParseItems(ctx context.Context, items []*plugins.Item) *menu.Menu {
	if len(items) == 0 {
		return nil
	}
	theMenu := menu.NewMenu()
	for _, item := range items {
		if item.Params.Separator {
			theMenu.Append(menu.Separator())
			continue
		}
		menuItem := m.ParseMenuItem(ctx, item)
		theMenu.Append(menuItem)
		if item.Alternate != nil {
			menuItem = m.ParseMenuItem(ctx, item.Alternate)
			theMenu.Append(menuItem)
		}
	}
	return theMenu
}

// ParseMenuItem parses a single item, returning the new menu.
func (m MenuParser) ParseMenuItem(ctx context.Context, item *plugins.Item) *menu.MenuItem {
	var err error
	displayText := item.DisplayText()
	itemAction := item.Action()
	menuItem := menu.Text(displayText, nil, func(_ *menu.CallbackData) {
		if itemAction == nil {
			return
		}
		itemAction(ctx)
	})
	if item.Text != displayText {
		tooltip := item.Text
		if item.Params.ANSI {
			tooltip, err = strconv.Unquote(`"` + tooltip + `"`)
			if err != nil {
				tooltip = item.Text
			}
		}
		if ansi.HasEscapeCodes(tooltip) {
			menuItem.Tooltip, err = ansi.Cleanse(tooltip)
			if err != nil {
				menuItem.Tooltip = err.Error()
			}
		} else {
			menuItem.Tooltip = tooltip
		}
	}
	if item.Params.Key != "" {
		acc, err := keys.Parse(item.Params.Key)
		if err != nil {
			// show the error in the menu
			menuItem.Label = fmt.Sprintf("error: %s", err)
		}
		menuItem.Accelerator = acc
	}
	menuItem.Image = item.Params.Image
	menuItem.FontName = item.Params.Font
	menuItem.FontSize = item.Params.Size
	menuItem.RGBA = item.Params.Color
	// Check for template image
	if item.Params.TemplateImage != "" {
		menuItem.Image = item.Params.TemplateImage
		// get template images working on all macOS versions
		menuItem.MacTemplateImage = true
	}
	menuItem.MacAlternate = item.Params.Alternate
	if item.Params.Dropdown == false {
		menuItem.Hidden = true
	}
	if len(item.Items) > 0 { // subitems
		menuItem.SubMenu = m.ParseItems(ctx, item.Items)
	}
	if itemAction == nil && menuItem.SubMenu == nil {
		// no action and no submenu, disable it.
		menuItem.Disabled = true
	}
	if item.Params.Disabled {
		// explicitly disabled
		menuItem.Disabled = true
	}
	return menuItem
}


================================================
FILE: app/menu_parser_test.go
================================================
package main

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/matryer/is"
	"github.com/matryer/xbar/pkg/plugins"
	"github.com/wailsapp/wails/v2/pkg/menu"
)

func TestMenuParser(t *testing.T) {
	is := is.New(t)

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
	defer cancel()

	plugin := &plugins.Plugin{}

	items := []*plugins.Item{
		{
			Plugin: plugin,
			Text:   "one",
			Items: []*plugins.Item{
				{Text: "sub1"},
				{Text: "sub2"},
				{Text: "sub3"},
			},
		},
		{
			Plugin: plugin,
			Text:   "two",
			Params: plugins.ItemParams{
				Font: "Courier New",
				Size: 26,
				Key:  "CmdOrCtrl+Shift+K",
			},
		},
		{
			Plugin: plugin,
			Text:   "three",
		},
		{
			Plugin: plugin,
			Text:   "four",
			Params: plugins.ItemParams{
				Href: "https://xbarapp.com",
			},
		},
		{
			Plugin: plugin,
			Text:   "five",
			Params: plugins.ItemParams{
				Shell:       "echo",
				ShellParams: []string{"hi"},
			},
		},
		{
			Plugin: plugin,
			Text:   "six",
			Params: plugins.ItemParams{
				Refresh: true,
			},
		},
		{
			Plugin: plugin,
			Text:   "seven but this will be truncated",
			Params: plugins.ItemParams{
				Refresh: true,
				Length:  5,
			},
		},
		{
			Plugin: plugin,
			Text:   "Template Image",
			Params: plugins.ItemParams{
				TemplateImage: "base64stuff",
			},
		},
		{
			Plugin: plugin,
			Text:   "Non Alternate",
			Alternate: &plugins.Item{
				Text: "Alternate",
				Params: plugins.ItemParams{
					Alternate: true,
				},
			},
		},
		{
			Text: "\033[0;38;2;255;0;0mA\033[0;38;2;255;127;0mN\033[0;38;2;255;255;0mS\033[0;38;2;0;255;0mI\u001B[0m",
			Params: plugins.ItemParams{
				Refresh: true,
			},
		},
	}
	menuitems := NewMenuParser().ParseItems(ctx, items)

	is.Equal(len(menuitems.Items), 11) // len(menuitems.Items)

	is.Equal(menuitems.Items[0].Label, "one")
	is.Equal(menuitems.Items[0].Disabled, false)
	is.True(menuitems.Items[0].SubMenu != nil)
	is.Equal(len(menuitems.Items[0].SubMenu.Items), 3)

	is.Equal(menuitems.Items[1].Label, "two")
	is.Equal(menuitems.Items[1].Disabled, true)
	is.Equal(menuitems.Items[1].FontName, "Courier New")
	is.Equal(menuitems.Items[1].FontSize, 26)
	is.True(menuitems.Items[1].Accelerator != nil)
	is.Equal(menuitems.Items[1].Accelerator.Key, "k")
	is.Equal(len(menuitems.Items[1].Accelerator.Modifiers), 2) // len(modifiers)

	is.Equal(menuitems.Items[2].Label, "three")
	is.Equal(menuitems.Items[2].Disabled, true)

	is.Equal(menuitems.Items[3].Label, "four")
	is.Equal(menuitems.Items[3].Disabled, false)

	is.Equal(menuitems.Items[4].Label, "five")
	is.Equal(menuitems.Items[4].Disabled, false)

	is.Equal(menuitems.Items[5].Label, "six")
	is.Equal(menuitems.Items[5].Disabled, false)

	is.Equal(menuitems.Items[6].Label, "seve…")
	is.Equal(menuitems.Items[6].Tooltip, "seven but this will be truncated")

	is.Equal(menuitems.Items[7].Label, "Template Image")
	is.Equal(menuitems.Items[7].Image, "base64stuff")
	//is.Equal(menuitems.Items[7].MacTemplateImage, true)

	is.Equal(menuitems.Items[8].Label, "Non Alternate")
	is.Equal(menuitems.Items[9].Label, "Alternate")
	is.Equal(menuitems.Items[9].MacAlternate, true)

	is.Equal(menuitems.Items[10].Tooltip, "ANSI")
}

func JSON(menu *menu.Menu, is *is.I) string {
	data, err := json.Marshal(menu)
	is.NoErr(err)
	return string(data)
}


================================================
FILE: app/package.json
================================================
{}


================================================
FILE: app/package.sh
================================================
#!/bin/bash

set -e

VERSION=`git describe --tags`

# usage:
#   git tag -a v0.1.0 -m "release tag."
#   git push origin v0.1.0
#   ./build.sh


# functions
requeststatus() { # $1: requestUUID
    requestUUID=${1?:"need a request UUID"}
    req_status=$(xcrun altool --notarization-info "$requestUUID" \
                              --username "${AC_USERNAME}" \
                              --password "${AC_PASSWORD}" 2>&1 \
                 | awk -F ': ' '/Status:/ { print $2; }' )
    echo "$req_status"
}

notarizefile() { # $1: path to file to notarize, $2: identifier
    filepath=${1:?"need a filepath"}
    identifier=${2:?"need an identifier"}
    
    # upload file
    echo "## uploading $filepath for notarization"
    requestUUID=$(xcrun altool --notarize-app \
                               --primary-bundle-id "$identifier" \
                               --username "${AC_USERNAME}" \
                               --password "${AC_PASSWORD}" \
                               --asc-provider "${AC_PROVIDER}" \
                               --file "$filepath" 2>&1 \
                  | awk '/RequestUUID/ { print $NF; }')
                               
    echo "Notarization RequestUUID: $requestUUID"
    
    if [[ $requestUUID == "" ]]; then 
        echo "could not upload for notarization"
        exit 1
    fi
        
    # wait for status to be not "in progress" any more
    request_status="in progress"
    while [[ "$request_status" == "in progress" ]]; do
        echo -n "waiting... "
        sleep 10
        request_status=$(requeststatus "$requestUUID")
        echo "$request_status"
    done
    
    # print status information
    xcrun altool --notarization-info "$requestUUID" \
                 --username "${AC_USERNAME}" \
                 --password "${AC_PASSWORD}"
    echo 
    
    if [[ $request_status != "success" ]]; then
        echo "## could not notarize $filepath"
        exit 1
    fi
    
}




echo ""
echo "  xbar ${VERSION}..."
echo ""
echo -n $VERSION > .version

# run all tests
./test.sh

rm -rf ./build/bin

sed "s/0.0.0/${VERSION}/" ./build/darwin/Info.plist.src > ./build/darwin/Info.plist
#CGO_LDFLAGS=-mmacosx-version-min=10.13 wails build -package -production -platform darwin/amd64 -o xbar
#CGO_LDFLAGS=-mmacosx-version-min=10.13 wails build -package -production -platform darwin/arm64 -o xbar
CGO_LDFLAGS=-mmacosx-version-min=10.13 wails build -package -production -platform darwin/universal -o xbar

cd ./build/bin/

echo "Signing the binary..."
codesign -s "${XBAR_SIGNING_IDENTITY}" -o runtime -v "./xbar.app/Contents/MacOS/xbar"

echo "Creating DMG..."
create-dmg ./xbar.app --overwrite --identity="${XBAR_SIGNING_IDENTITY}" --dmg-title "Install xbar"
mv xbar*.dmg "xbar.${VERSION}.dmg"

#echo "TARing..."
#tar -czvf xbar.${VERSION}.tar.gz ./xbar.app

echo "Zipping..."
zip -r xbar.zip ./xbar.app
mv xbar.zip "xbar.${VERSION}.zip"

#xcrun notarytool submit "xbar.${VERSION}.zip" --keychain-profile "${AC_PASSWORD}" --wait
echo "Notorizing..."

notarizefile "xbar.${VERSION}.zip" "com.xbarapp.app"
notarizefile "xbar.${VERSION}.dmg" "com.xbarapp.app"
xcrun stapler staple "xbar.${VERSION}.dmg"

rm -rf ./build/bin/xbar.app

open .


================================================
FILE: app/person_service.go
================================================
package main

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/matryer/xbar/pkg/metadata"
)

// PersonService provides people related functionality.
type PersonService struct {
	baseURL string
	Client  *http.Client
}

// NewPersonService makes a new PersonService.
func NewPersonService(client *http.Client) *PersonService {
	return &PersonService{
		Client:  client,
		baseURL: "https://xbarapp.com/docs",
	}
}

// PersonDetails are details relating to a human.
type PersonDetails struct {
	Person  *metadata.Person  `json:"person"`
	Plugins []metadata.Plugin `json:"plugins"`
}

// GetPersonDetails gets details about a human by their GitHub username.
func (p *PersonService) GetPersonDetails(githubUsername string) (*PersonDetails, error) {
	req, err := http.NewRequest("GET", p.baseURL+"/contributors/"+githubUsername+".json", nil)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(req.Context(), apiRequestTimeout)
	defer cancel()
	req = req.WithContext(ctx)
	res, err := p.Client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	var payload *PersonDetails
	err = json.Unmarshal(body, &payload)
	if err != nil {
		return nil, err
	}
	return payload, nil
}


================================================
FILE: app/plugins_service.go
================================================
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/matryer/xbar/pkg/metadata"
	"github.com/matryer/xbar/pkg/plugins"
	"github.com/pkg/errors"
	wails "github.com/wailsapp/wails/v2"
	"github.com/wailsapp/wails/v2/pkg/options/dialog"
)

// PluginsService access remote plugin information.
type PluginsService struct {
	runtime *wails.Runtime
	baseURL string

	client *http.Client

	// osLock is used whenever there are operating system changes,
	// like renaming files. This prevents overlap and potentially strange
	// state.
	// todo: move this to a better place?
	osLock sync.Mutex

	// OnRefresh is called whenever the menus should
	// be updated.
	OnRefresh func()
}

// NewPluginsService makes a new PluginsService.
func NewPluginsService(client *http.Client, baseURL string) *PluginsService {
	return &PluginsService{
		baseURL: baseURL,
		client:  client,
	}
}

// GetPlugins gets the plugins for the specified category.
func (p *PluginsService) GetPlugins(categoryPath string) ([]metadata.Plugin, error) {
	req, err := http.NewRequest("GET", p.baseURL+categoryPath+"/plugins.json", nil)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(req.Context(), apiRequestTimeout)
	defer cancel()
	req = req.WithContext(ctx)
	res, err := p.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	var payload struct {
		Plugins []metadata.Plugin
	}
	err = json.Unmarshal(body, &payload)
	if err != nil {
		return nil, err
	}
	return payload.Plugins, nil
}

// GetPlugin gets the plugin metadata for a plugin.
func (p *PluginsService) GetPlugin(pluginPath string) (*metadata.Plugin, error) {
	req, err := http.NewRequest("GET", p.baseURL+pluginPath+".json", nil)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(req.Context(), apiRequestTimeout)
	defer cancel()
	req = req.WithContext(ctx)
	res, err := p.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	var payload struct {
		Plugin *metadata.Plugin
	}
	err = json.Unmarshal(body, &payload)
	if err != nil {
		return nil, err
	}
	return payload.Plugin, nil
}

// GetFeaturedPlugins gets the featured plugins.
func (p *PluginsService) GetFeaturedPlugins() ([]metadata.Plugin, error) {
	req, err := http.NewRequest("GET", p.baseURL+"featured-plugins.json", nil)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithTimeout(req.Context(), apiRequestTimeout)
	defer cancel()
	req = req.WithContext(ctx)
	res, err := p.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}
	var payload struct {
		Plugins []metadata.Plugin
	}
	err = json.Unmarshal(body, &payload)
	if err != nil {
		return nil, err
	}
	return payload.Plugins, nil
}

// GetInstalledPlugins gets the installed plugins.
func (p *PluginsService) GetInstalledPlugins() ([]plugins.InstalledPlugin, error) {
	p.osLock.Lock()
	defer p.osLock.Unlock()
	return plugins.GetInstalledPlugins(pluginDirectory)
}

// InstallPlugin installs the plugin described by the provided metadata.
func (p *PluginsService) InstallPlugin(plugin metadata.Plugin) (string, error) {
	defer p.OnRefresh()
	p.osLock.Lock()
	defer p.osLock.Unlock()
	if p.runtime != nil {
		response, err := p.runtime.Dialog.Message(&dialog.MessageDialog{
			Type:          "Question",
			Title:         "Install plugin",
			Message:       fmt.Sprintf("Are you sure you want to install %s?", plugin.Title),
			Buttons:       []string{"Install", "Cancel"},
			DefaultButton: "Install",
			CancelButton:  "Cancel",
		})
		if err != nil {
			return "", err
		}
		switch response {
		case "Install":
			// continue
		case "Cancel":
			return "", nil
		}
	}
	installer := &plugins.Installer{
		Client: &http.Client{
			Timeout: 1 * time.Minute,
		},
		PluginDir: pluginDirectory,
	}
	pluginPath := "https://xbarapp.com/docs/plugins/" + plugin.Path + ".json"
	pluginPathURL, err := url.Parse(pluginPath)
	if err != nil {
		return "", errors.Wrapf(err, "parse URL: %s", pluginPath)
	}
	installedPluginPath, err := installer.Install(pluginPathURL)
	if err != nil {
		return "", errors.Wrap(err, "Install")
	}
	tickOS() // wait a beat
	return installedPluginPath, nil
}

// UninstallPluginRequest is the object to send when uninstalling an
// installed plugin.
type UninstallPluginRequest struct {
	Path  string
	Title string
}

// UninstallPlugin removes a plugin.
func (p *PluginsService) UninstallPlugin(installedPluginInfo UninstallPluginRequest) (bool, error) {
	defer p.OnRefresh()
	p.osLock.Lock()
	defer p.osLock.Unlock()
	if p.runtime != nil {
		response, err := p.runtime.Dialog.Message(&dialog.MessageDialog{
			Type:          "Question",
			Title:         "Uninstall plugin",
			Message:       fmt.Sprintf("Are you sure you want to remove %s?\n\nThis cannot be undone.", installedPluginInfo.Title),
			Buttons:       []string{"Uninstall", "Cancel"},
			DefaultButton: "Uninstall",
			CancelButton:  "Cancel",
		})
		if err != nil {
			return false, err
		}
		switch response {
		case "Uninstall":
			// continue
		case "Cancel":
			return false, nil
		}
	}
	installer := &plugins.Installer{
		PluginDir: pluginDirectory,
	}
	err := installer.Uninstall(installedPluginInfo.Path)
	if err != nil {
		return false, errors.Wrap(err, "uninstall")
	}
	tickOS() // wait a beat
	return true, nil
}

// InstalledPluginMetadata is the metadata extracted from an installed
// plugin.
type InstalledPluginMetadata struct {
	Plugin          metadata.Plugin         `json:"plugin"`
	Enabled         bool                    `json:"enabled"`
	RefreshInterval plugins.RefreshInterval `json:"refreshInterval"`
	Error           string                  `json:"error,omitempty"`
}

// GetInstalledPluginMetadata loads the plugin metadata from a plugin file.
func (p *PluginsService) GetInstalledPluginMetadata(installedPluginPath string) (*InstalledPluginMetadata, error) {
	p.osLock.Lock()
	defer p.osLock.Unlock()
	filename := filepath.Base(installedPluginPath)
	b, err := os.ReadFile(filepath.Join(pluginDirectory, installedPluginPath))
	if err != nil {
		return nil, err
	}
	md, err := metadata.Parse(metadata.DebugfNoop, filename, string(b))
	if err != nil {
		return nil, err
	}
	md.Path = installedPluginPath
	response := &InstalledPluginMetadata{
		Plugin:  md,
		Enabled: plugins.IsPluginEnabled(installedPluginPath),
	}
	response.RefreshInterval, err = plugins.ParseFilenameInterval(filename)
	if err != nil {
		response.Error = err.Error()
	}
	return response, nil
}

// LoadVariableValues loads the values for an installed plugin.
func (p *PluginsService) LoadVariableValues(installedPluginPath string) (map[string]interface{}, error) {
	p.osLock.Lock()
	defer p.osLock.Unlock()
	defer tickOS() // wait a beat
	return plugins.LoadVariableValues(pluginDirectory, installedPluginPath)
}

// SaveVariableValues saves the values for an installed plugin.
func (p *PluginsService) SaveVariableValues(installedPluginPath string, values map[string]interface{}) error {
	p.osLock.Lock()
	defer p.osLock.Unlock()
	defer tickOS() // wait a beat
	return plugins.SaveVariableValues(pluginDirectory, installedPluginPath, values)
}

// SetEnabled sets a plugin to enabled or disabled state, depending on the value of
// the enabled parameter.
func (p *PluginsService) SetEnabled(installedPluginPath string, enabled bool) (string, error) {
	defer p.OnRefresh()
	p.osLock.Lock()
	defer p.osLock.Unlock()
	newPath, err := plugins.SetEnabled(pluginDirectory, installedPluginPath, enabled)
	if err != nil {
		return "", err
	}
	tickOS() // wait a beat
	return newPath, err
}

// SetRefreshIntervalResult is the refresh interval result returned from SetRefreshInterval.
type SetRefreshIntervalResult struct {
	InstalledPluginPath string                  `json:"installedPluginPath"`
	RefreshInterval     plugins.RefreshInterval `json:"refreshInterval"`
}

// SetRefreshInterval updates the refresh interval for a plugin.
func (p *PluginsService) SetRefreshInterval(installedPluginPath string, refreshInterval plugins.RefreshInterval) (*SetRefreshIntervalResult, error) {
	defer p.OnRefresh()
	p.osLock.Lock()
	defer p.osLock.Unlock()
	newPath, newInterval, err := plugins.SetRefreshInterval(pluginDirectory, installedPluginPath, refreshInterval)
	if err != nil {
		return nil, err
	}
	result := &SetRefreshIntervalResult{
		InstalledPluginPath: newPath,
		RefreshInterval:     newInterval,
	}
	tickOS() // wait a beat
	return result, nil
}


================================================
FILE: app/settings.go
================================================
package main

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"

	"github.com/pkg/errors"
)

type settings struct {
	sync.Mutex

	path string `json:"-"`

	// AutoUpdate indicates that xbar should automatically
	// update itself.
	AutoUpdate bool `json:"autoupdate"`

	Terminal struct {
		AppleScriptTemplate3 string `json:"appleScriptTemplate3"`
	} `json:"terminal"`
}

func (s *settings) setDefaults() {
	if s.Terminal.AppleScriptTemplate3 == "" {
		s.Terminal.AppleScriptTemplate3 = `
			set quotedScriptName to quoted form of "{{ .Command }}"
		{{ if .Params }}
			set commandLine to {{ .Vars }} & " " & quotedScriptName & " " & {{ .Params }}
		{{ else }}
			set commandLine to {{ .Vars }} & " " & quotedScriptName
		{{ end }}
			if application "Terminal" is running then 
				tell application "Terminal"
					do script commandLine
					activate
				end tell
			else
				tell application "Terminal"
					do script commandLine in window 1
					activate
				end tell
			end if
		`
	}
}

func loadSettings(path string) (*settings, error) {
	s := &settings{
		path: path,
	}
	b, err := ioutil.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			// file not found - it's ok, just use defaults
			s.setDefaults()
			return s, nil
		}
		return nil, errors.Wrap(err, "ReadFile")
	}
	err = json.Unmarshal(b, s)
	if err != nil {
		return nil, errors.Wrap(err, "Unmarshal")
	}
	s.setDefaults()
	return s, nil
}

func (s *settings) save() error {
	s.Lock()
	defer s.Unlock()
	s.setDefaults()
	b, err := json.MarshalIndent(s, "", "\t")
	if err != nil {
		return errors.Wrap(err, "MarshalIndent")
	}
	err = os.MkdirAll(filepath.Dir(s.path), 0777)
	if err != nil {
		return errors.Wrap(err, "MkdirAll")
	}
	err = ioutil.WriteFile(s.path, b, 0777)
	if err != nil {
		return errors.Wrap(err, "WriteFile")
	}
	return nil
}


================================================
FILE: app/settings_test.go
================================================
package main

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/matryer/is"
)

func TestSettings(t *testing.T) {
	is := is.New(t)

	t.Cleanup(func() {
		os.RemoveAll(filepath.Join("testdata", "settings.json"))
	})

	s, err := loadSettings(filepath.Join("testdata", "settings.json"))
	is.NoErr(err)
	s.AutoUpdate = true

	err = s.save()
	is.NoErr(err)

	s, err = loadSettings(filepath.Join("testdata", "settings.json"))
	is.NoErr(err)
	is.Equal(s.AutoUpdate, true)

}


================================================
FILE: app/test.sh
================================================
#!/bin/bash

set -e

VERSION=`git describe --tags`
echo -n $VERSION > .version

go test

cd ../pkg/metadata
go test
cd ../../app

cd ../pkg/plugins
go test
cd ../../app

cd ../pkg/update
go test
cd ../../app

# cd ../tools/sitegen
# echo -n $VERSION > .version
# go test -short
# cd ../../app


================================================
FILE: app/wails.json
================================================
{
    "name": "xbar",
    "outputfilename": "xbar",
    "html": "frontend/public/index.html",
    "js": "frontend/public/bundle.js",
    "css": "frontend/public/bundle.css",
    "frontend:build": "npm run build",
    "frontend:install": "npm install"
}


================================================
FILE: archive/bitbar/.gitignore
================================================
# xcode noise

build/*
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
profile
*.moved-aside

# osx noise
.DS_Store
profile
build

================================================
FILE: archive/bitbar/.gitmodules
================================================
[submodule "App/Vendor/STPrivilegedTask"]
	path = App/Vendor/STPrivilegedTask
	url = https://github.com/sveinbjornt/STPrivilegedTask.git
[submodule "App/Vendor/DateTools"]
	path = App/Vendor/DateTools
	url = https://github.com/MatthewYork/DateTools.git
[submodule "App/Vendor/LaunchAtLoginController"]
	path = App/Vendor/LaunchAtLoginController
	url = https://github.com/ksuther/LaunchAtLoginController--with-ARC-.git
[submodule "App/Vendor/AHProxySettings"]
	path = App/Vendor/AHProxySettings
	url = https://github.com/eahrold/AHProxySettings.git
[submodule "App/Vendor/NSStringEmojize"]
	path = App/Vendor/NSStringEmojize
	url = https://github.com/diy/NSStringEmojize.git
[submodule "App/Vendor/Sparkle"]
	path = App/Vendor/Sparkle
	url = https://github.com/sparkle-project/Sparkle.git


================================================
FILE: archive/bitbar/.travis.yml
================================================
language: objective-c
osx_image: xcode11.2
xcode_project: App/BitBar.xcodeproj
xcode_scheme: BitBar
xcode_sdk: macosx10.11
#before_install:
#- brew update
#- brew outdated xctool || brew upgrade xctool
script:
- CERT_P12=Certificate.p12
- echo "$CERT_BASE64" | base64 --decode > $CERT_P12
- KEYCHAIN=build.keychain
- security create-keychain -p travis $KEYCHAIN
- security default-keychain -s $KEYCHAIN
- security unlock-keychain -p travis $KEYCHAIN
- security set-keychain-settings -t 3600 -u $KEYCHAIN
- security import $CERT_P12 -k $KEYCHAIN -P "$CERT_PW" -T /usr/bin/codesign
- 'IDENTITY="Developer ID Application: Code and That Ltd (B3T8QSC4HG)"'
- if [ -n "$TRAVIS_TAG" ]; then xctool -project $TRAVIS_XCODE_PROJECT
  -scheme $TRAVIS_XCODE_SCHEME -sdk $TRAVIS_XCODE_SDK -configuration Release OBJROOT=$PWD/build
  SYMROOT=$PWD/build ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="$IDENTITY" build analyze; else xctool -project $TRAVIS_XCODE_PROJECT
  -scheme $TRAVIS_XCODE_SCHEME -sdk $TRAVIS_XCODE_SDK -configuration Release OBJROOT=$PWD/build
  SYMROOT=$PWD/build ONLY_ACTIVE_ARCH=NO build analyze -failOnWarnings; fi
- security delete-keychain $KEYCHAIN
before_deploy:
- OUTPUTDIR="$PWD/build/Release"
- cd $OUTPUTDIR
- ditto -c -k --sequesterRsrc --keepParent "BitBar.app" "BitBar-$TRAVIS_TAG.zip"
- ditto -c -k --sequesterRsrc --keepParent "BitBarDistro.app" "BitBarDistro-$TRAVIS_TAG.zip"
deploy:
  provider: releases
  api_key:
    secure: VB7wqPRAmwRxX1ugTss4lWdcCjMO4+9yYuvkSKIhRz5PcKFTdgIE5Ol29wssYSlEnk1D5ZqeCJBe3t2qowrxOKHWKJRxH5r4fbgYAYnbk9/nWsMLgWDn1mo4nYa0sD4GyMUDY9JqqmtBY3nZ2pYcJ0L1LmxUU+EHViwcBQz6G4Y=
  file:
  - $OUTPUTDIR/BitBar-$TRAVIS_TAG.zip
  - $OUTPUTDIR/BitBarDistro-$TRAVIS_TAG.zip
  skip_cleanup: true
  on:
    repo: matryer/bitbar
    tags: true
after_deploy:
# Rebuild the Sparkle feed
- curl -s -X POST -H "Authorization:token $GH_TOKEN" -H Accept:application/vnd.github.mister-fantastic-preview https://api.github.com/repos/matryer/bitbar/pages/builds
# Update the Sparkle feed cache
- brew install jq
- PRERELEASE=$(curl -s -H "Authorization:token $GH_TOKEN" "https://api.github.com/repos/matryer/bitbar/releases/tags/$TRAVIS_TAG" | jq .prerelease)
- echo "$PRERELEASE"
- if [ "$PRERELEASE" = true ]; then FEED=(beta distro-beta); else FEED=(bitbar distro); fi
- while :; do STATUS=$(curl -s -H "Authorization:token $GH_TOKEN" https://api.github.com/repos/matryer/bitbar/pages | jq .status); if [ "$STATUS" != '"queued"' ] && [ "$STATUS" != '"building"' ]; then echo "$STATUS"; break; fi; sleep 1; done
- curl -s -H "X-RELOAD-KEY:$SPARKLE_UPDATE_KEY" -D - -o /dev/null "https://bitbarapp.com/feeds/${FEED[0]}/reload"
- curl -s -H "X-RELOAD-KEY:$SPARKLE_UPDATE_KEY" -D - -o /dev/null "https://bitbarapp.com/feeds/${FEED[1]}/reload"
notifications:
  slack:
    secure: TpJVJf/NWxDvHxPjaQJnVg4vlW6JeLQ7eWadzFo7sUNSdB7Tui703AvYjG8tZTlk2lJ/4bV4jKgz8+rSElkeGsfPLTgDU33IDmG3V9o6MrAeV/ZQL37793bj+zJFxNuOkoIBaBQt1aNDTuIDUB97MNv02Vklb1M7Yd44NRVXdHk=


================================================
FILE: archive/bitbar/App/BitBar/App.xib
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13A603" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
    <dependencies>
        <deployment defaultVersion="1060" identifier="macosx"/>
        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
    </dependencies>
    <objects>
        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"/>
        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
        <customObject id="-3" userLabel="Application">
            <connections>
                <outlet property="delegate" destination="GkJ-bc-8Fw" id="qWn-q3-AWW"/>
            </connections>
        </customObject>
        <customObject id="GkJ-bc-8Fw" customClass="AppDelegate"/>
    </objects>
</document>

================================================
FILE: archive/bitbar/App/BitBar/AppDelegate.m
================================================
//
//  AppDelegate.m
//  BitBar
//
//  Created by Mat Ryer on 11/12/13.
//  Copyright (c) 2013 Bit Bar. All rights reserved.
//

#import "NSUserDefaults+Settings.h"
#import "LaunchAtLoginController.h"
#import "PluginManager.h"
#import "Plugin.h"
#import <Sparkle/Sparkle.h>
#import <sys/stat.h>

@interface AppDelegate : NSObject <NSApplicationDelegate, NSURLDownloadDelegate, SUUpdaterDelegate>

@property (assign) IBOutlet NSWindow *window;

@property PluginManager *pluginManager;

// plugin download
@property NSURLDownload *download;
@property NSString *destinationPath;
@property NSString *suggestedDestinationPath;

@end

@implementation AppDelegate

- (NSArray*) otherCopies { return [NSRunningApplication runningApplicationsWithBundleIdentifier:NSBundle.mainBundle.bundleIdentifier]; }

- (void)applicationWillFinishLaunching:(NSNotification *)n {
  NSString *feedURLString;
#ifdef DISTRO
  feedURLString = @"https://bitbarapp.com/feeds/distro";
#else
  feedURLString = @"https://bitbarapp.com/feeds/bitbar";
#endif
  
  SUUpdater *updater = [SUUpdater sharedUpdater];
  updater.delegate = self;
  updater.automaticallyChecksForUpdates = YES;
  updater.feedURL = [NSURL URLWithString:feedURLString];
  updater.sendsSystemProfile = YES;
  
  // register custom url scheme handler
  [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
                                                     andSelector:@selector(handleGetURLEvent:withReplyEvent:)
                                                   forEventClass:kInternetEventClass
                                                      andEventID:kAEGetURL];

  if (self.otherCopies.count <= 1) return;
  NSModalResponse runm = [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Another copy of %@ is already running.", NSBundle.mainBundle.infoDictionary[(NSString *)kCFBundleNameKey]]
                   defaultButton:@"Quit" alternateButton:@"Kill others" otherButton:nil informativeTextWithFormat:@"Quit, or kill the other copy(ies)?"] runModal];

  runm == 1 ? [NSApp terminate:nil] : ({

  for ( NSRunningApplication *app in self.otherCopies)
    if (app.processIdentifier != NSProcessInfo.processInfo.processIdentifier)
      [app terminate];

  });
}

- (void) applicationDidFinishLaunching:(NSNotification*)n {

  // enable usage of Safari's WebInspector to debug HTML Plugins
  [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"WebKitDeveloperExtras"];
  [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
                                                         selector: @selector(receiveWakeNote:)
                                                             name: NSWorkspaceDidWakeNotification object: NULL];

  if (DEFS.isFirstTimeAppRun) {
    LaunchAtLoginController *launcher = LaunchAtLoginController.new;
    if (!launcher.launchAtLogin) [launcher setLaunchAtLogin:YES];
    DEFS.isFirstTimeAppRun = NO;
  }

  NSString* pluginsDirectory = DEFS.pluginsDirectory;

  // Test if we are running unit tests!
  NSString* testBundlePath = [NSProcessInfo processInfo].environment[@"XCInjectBundle"];
  if (testBundlePath && [testBundlePath hasSuffix:@".xctest"]) {
    pluginsDirectory = [[[NSBundle bundleWithPath:testBundlePath] resourcePath] stringByAppendingPathComponent:@"TestPlugins"];
  }
  
  // make a plugin manager
  [_pluginManager = [PluginManager.alloc initWithPluginPath:pluginsDirectory]
                                                                  setupAllPlugins];
}

- (void) receiveWakeNote: (NSNotification*) note
{
  [[self pluginManager] reset];
}

- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
  // check if plugins directory is set
  if (!DEFS.pluginsDirectory)
    return;
  
  // extract the url from the event and handle it
  
  NSString *URLString = [event paramDescriptorForKeyword:keyDirectObject].stringValue;
  URLString = [URLString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  NSString *prefix = @"bitbar://refreshPlugin?name=";
  
  if ([URLString hasPrefix:prefix]) {
      URLString = [URLString substringFromIndex:prefix.length];
      NSArray *plugins = [self.pluginManager.plugins filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"name LIKE %@", URLString]];
      [plugins makeObjectsPerformSelector:@selector(performRefreshNow) withObject:nil];
      return;
  }
  
  // don't open plugins if user configuration is disabled
  if (DEFS.userConfigDisabled)
    return;
  
  prefix = @"bitbar://openPlugin?";
  
  // skip urls that don't begin with our prefix
  if (![URLString hasPrefix:prefix])
    return;
  
  URLString = [URLString substringFromIndex:prefix.length];
  prefix = @"title=";
  
  NSString *title = nil;
  
  if ([URLString hasPrefix:prefix]) {
    URLString = [URLString substringFromIndex:prefix.length];
    NSArray *components = [URLString componentsSeparatedByString:@"&"];
    
    if (components.count < 2)
      return;
      
    title = components.firstObject;
    URLString = [[components subarrayWithRange:NSMakeRange(1, components.count - 1)] componentsJoinedByString:@"&"];
  }
  
  prefix = @"src=";
  
  if (![URLString hasPrefix:prefix])
    return;
  
  URLString = [URLString substringFromIndex:prefix.length];
  
  BOOL trusted = NO;
  
  // if the plugin is at our repository, only display the filename
  if ([URLString hasPrefix:@"https://github.com/matryer/xbar-plugins/raw/master/"]) {
    trusted = YES;
  }
  
  NSAlert *alert = [[NSAlert alloc] init];
  [alert addButtonWithTitle:@"Install"];
  [alert addButtonWithTitle:@"Cancel"];
  alert.messageText = [NSString stringWithFormat:@"Download and install the plugin %@?", trusted ? (title.length > 0 ? title : URLString.lastPathComponent) : [NSString stringWithFormat:@"at %@", URLString]];

    if (trusted) {
        alert.informativeText = @"Only install plugins from trusted sources.";
    } else {
        alert.informativeText = @"CAUTION: This plugin is not from the official BitBar repository. We recommend that you only install plugins from trusted sources.";
    }

  if ([alert runModal] != NSAlertFirstButtonReturn) {
    // cancel clicked
    return;
  }
  
  [self.download cancel];
  self.destinationPath = nil;
  self.suggestedDestinationPath = nil;
  
  // NSURLSession is not available below 10.9 :(
  self.download = [[NSURLDownload alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:URLString]] delegate:self];
}

#pragma mark - NSURLDownload delegate

- (void)download:(NSURLDownload *)download decideDestinationWithSuggestedFilename:(NSString *)filename {
  self.suggestedDestinationPath = [DEFS.pluginsDirectory stringByAppendingPathComponent:filename];
  [download setDestination:self.suggestedDestinationPath allowOverwrite:NO];
}

- (void)download:(NSURLDownload *)download didCreateDestination:(NSString *)path {
  self.destinationPath = path;
}

- (void)downloadDidFinish:(NSURLDownload *)download {
  if (self.destinationPath) {
    if (self.suggestedDestinationPath && ![self.suggestedDestinationPath isEqualToString:self.destinationPath]) {
      // overwrite file at suggested destination path
      
      [[NSFileManager defaultManager] removeItemAtPath:self.suggestedDestinationPath error:nil];
      
      if ([[NSFileManager defaultManager] moveItemAtPath:self.destinationPath toPath:self.suggestedDestinationPath error:nil])
        self.destinationPath = self.suggestedDestinationPath;
    }
    
    // ensure plugin is executable
    
    // `chmod +x plugin.sh`
    struct stat st;
    stat(self.destinationPath.UTF8String, &st);
    chmod(self.destinationPath.UTF8String, (st.st_mode & ALLPERMS) | S_IXUSR | S_IXGRP | S_IXOTH);
  }
  
  // refresh
  [self.pluginManager reset];

  self.download = nil;
  self.destinationPath = nil;
  self.suggestedDestinationPath = nil;
}

- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error {
  NSAlert *alert = [[NSAlert alloc] init];
  [alert addButtonWithTitle:@"OK"];
  alert.messageText = @"Download failed";
  alert.informativeText = error.localizedDescription;
  [alert runModal];
  
  self.download = nil;
  self.destinationPath = nil;
  self.suggestedDestinationPath = nil;
}

#pragma mark - SUUpdater delegate

// hack to disable the update prompt if user configuration is disabled
- (SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forUpdater:(SUUpdater *)updater {
  id driver;
  
  if (DEFS.userConfigDisabled || ((driver = [updater valueForKey:@"driver"]) && !driver)) return nil;
  
  SEL sel = NSSelectorFromString(@"bestItemFromAppcastItems:getDeltaItem:withHostVersion:comparator:");
  NSArray *items = appcast.items;
  void *deltaUpdateItem = nil;
  id version = [[driver valueForKey:@"host"] valueForKey:@"version"];
  id comparator = [driver valueForKey:@"versionComparator"];
  void *item = nil;
  
  NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[[driver class] methodSignatureForSelector:sel]];
  inv.selector = sel;
  inv.target = [driver class];
  if (items) [inv setArgument:&items atIndex:2];
  [inv setArgument:&deltaUpdateItem atIndex:3];
  if (version) [inv setArgument:&version atIndex:4];
  if (comparator) [inv setArgument:&comparator atIndex:5];
  [inv invoke];
  
  if (deltaUpdateItem) {
    item = deltaUpdateItem;
  } else {
    [inv getReturnValue:&item];
  }
  
  return (__bridge SUAppcastItem *)item;
}

@end

int main(int argc, const char * argv[]) { return NSApplicationMain(argc, argv); }


================================================
FILE: archive/bitbar/App/BitBar/Base.lproj/MainMenu.xib
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13A603" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
    </dependencies>
    <objects>
        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
            <connections>
                <outlet property="delegate" destination="494" id="495"/>
            </connections>
        </customObject>
        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
        <customObject id="-3" userLabel="Application"/>
        <customObject id="494" customClass="AppDelegate"/>
        <customObject id="420" customClass="NSFontManager"/>
    </objects>
</document>

================================================
FILE: archive/bitbar/App/BitBar/BitBar-Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>${EXECUTABLE_NAME}</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>${PRODUCT_NAME}</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(MARKETING_VERSION)</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Viewer</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>bitbar</string>
			</array>
		</dict>
	</array>
	<key>CFBundleVersion</key>
	<string>$(CURRENT_PROJECT_VERSION)</string>
	<key>LSApplicationCategoryType</key>
	<string>public.app-category.productivity</string>
	<key>LSMinimumSystemVersion</key>
	<string>${MACOSX_DEPLOYMENT_TARGET}</string>
	<key>LSUIElement</key>
	<true/>
	<key>NSHumanReadableCopyright</key>
	<string>Copyright ©2013-2016 Mat Ryer. All rights reserved.</string>
	<key>NSMainNibFile</key>
	<string>App</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
</dict>
</plist>


================================================
FILE: archive/bitbar/App/BitBar/CHANGELOG.md
================================================
# BitBar Changes

## v1.9.2

* Added Sparkle updater

## v1.9.1

* Bug fixes and performance enhancements

## v1.9

* Restored distributable BitBar
* BitBar now requires OS X 10.7 to fix a crash on refresh and wake from sleep
* Added support for single click action from the status bar (`href`, `bash` and `refresh`)
* Added support for separators in sub menus
* Added update check (Preferences menu will show you when there's a newer version available)
* Various other bug fixes

## v1.8

* Added `emojize` parameter
* Added sub menus
* Added ANSI color code support and `ansi=false` option to turn it off
* Significant bug fixes and performance enhancements

## v1.7

* Added `image` and `templateImage` parameters (Base64 encoded images)
* Memory leak fixes and bug fixes

## v1.6

* Added [distributable BitBar](https://github.com/matryer/bitbar/blob/master/Docs/DistributingBitBar.md) to allow you to bundle a BitBar app with plugins, and distribute a tamper free version.
* Added ability to refresh plugins remotely via the [URL scheme](https://github.com/matryer/bitbar/blob/master/Docs/URLScheme.md) - #149 and #216
* Integration with Slack - please join us: [![Slack Status](https://getbitbar.herokuapp.com/badge.svg)](https://getbitbar.herokuapp.com/)
* Added ability to [hide Preferences menu](https://github.com/matryer/bitbar/blob/master/Docs/DistributingBitBar.md#settings).
* Small UI improvements based on feedback from lovely users just like you
* Updated dependencies to fix crash and memory leak

* See a [complete list of all changes in this release](https://github.com/matryer/bitbar/compare/v1.5.1...master)

## v1.5

Features:

* Added `trim=false` option to give plugin authors control of whitespace - #182
* Added `alternate=true` option to allow option key menu items - #218
* Tasks now run in background (preventing menu bar items from locking) - #181
* Plugins will reset on wake from sleep - #184
* Added URL scheme for opening and downloading plugins - #224

Other:

* Work to address duplicate items - #21
* Updated "Browse plugins..." link to getbitbar.com
* Improved docs
* "Open at login" was getting reset every time - now it's remembered - #169
* Warnings and static analyzer errors are resolved

## v1.4

Features:

* Made `$BitBarDarkMode` environment variable available to plugins - #155
* Ability to hide items from the dropdown - #102
* Selecting a Plugin folder (rather than having to navigate inside it) is enough - better UI - #120
* `Reset` renamed to `Refresh` to make it clearer - #82
* `refresh` parameter indicates that an item should issue the refresh of a plugin - #48
* Added support for `length` parameter - #131

Other:

* Started tracking changes
* Fixed vertical alignment - #153
* Numbers are now monospace - #148
* Removed unnecessary separators from BitBar menu - #143
* General bug fixes and improvements
* Support spaces in paths
* Fonts will be checked, and a default will be used if they're not available
* Plugins now live in their own repo - #78


================================================
FILE: archive/bitbar/App/BitBar/ExecutablePlugin.h
================================================
//
//  ExecutablePlugin.h
//  BitBar
//
//  Created by Mathias Leppich on 22/01/14.
//  Copyright (c) 2014 Bit Bar. All rights reserved.
//

#import "Plugin.h"

@interface ExecutablePlugin : Plugin

@property (nonatomic, strong) NSTimer *lineCycleTimer;
@property (nonatomic, strong) NSTimer *refreshTimer;

- (BOOL) refreshContentByExecutingCommand;

@end


================================================
FILE: archive/bitbar/App/BitBar/ExecutablePlugin.m
================================================
//
//  ExecutablePlugin.m
//  BitBar
//
//  Created by Mathias Leppich on 22/01/14.
//  Copyright (c) 2014 Bit Bar. All rights reserved.
//

#import "ExecutablePlugin.h"
#import "PluginManager.h"
#import "NSTask+useSystemProxies.h"
#import "NSUserDefaults+Settings.h"

@implementation ExecutablePlugin

- (BOOL) refreshContentByExecutingCommand {

  if (![[NSFileManager defaultManager] fileExistsAtPath:self.path]) {
    return NO;
  }

  NSTask *task = NSTask.new;

  [task setEnvironment:self.manager.environment];
  [task setLaunchPath:self.path];
  [task useSystemProxies];

  NSPipe *stdoutPipe = [NSPipe pipe];
  [task setStandardOutput:stdoutPipe];

  NSPipe *stderrPipe = [NSPipe pipe];
  [task setStandardError:stderrPipe];

  @try {
    [task launch];
  } @catch (NSException *e) {
    NSLog(@"Error when running %@: %@", self.name, e);
    self.lastCommandWasError = YES;
    self.content = @"";
    self.errorContent = e.reason;
    return NO;
  }
  NSData *stdoutData = [[stdoutPipe fileHandleForReading] readDataToEndOfFile];
  NSData *stderrData = [[stderrPipe fileHandleForReading] readDataToEndOfFile];

  [task waitUntilExit];

  self.content = [NSString.alloc initWithData:stdoutData encoding:NSUTF8StringEncoding];
  self.errorContent = [NSString.alloc initWithData:stderrData encoding:NSUTF8StringEncoding];

  // failure
  if ([task terminationStatus] != 0) {
    self.lastCommandWasError = YES;
    return NO;
  }

  // success
  self.lastCommandWasError = NO;
  return YES;
}

- (void)performRefreshNow {
  self.content = @"Updating ...";
  self.errorContent = @"";
  [self rebuildMenuForStatusItem:self.statusItem];
  self.currentLine = -1;
  [self cycleLines];
  [self.manager pluginDidUdpdateItself:self];
  [self refresh];
}

-(BOOL)refresh {
  __weak ExecutablePlugin *weakSelf = self;
  [self.lineCycleTimer invalidate];
  self.lineCycleTimer = nil;
  [self.refreshTimer invalidate];
  self.refreshTimer = nil;

  // execute command
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),  ^{
    [weakSelf refreshContentByExecutingCommand];
    dispatch_sync(dispatch_get_main_queue(), ^{
      if (weakSelf) {
        __strong ExecutablePlugin* strongSelf = weakSelf;
        
        strongSelf.lastUpdated = NSDate.new;
        
        [strongSelf rebuildMenuForStatusItem:strongSelf.statusItem];
        
        // reset the current line
        strongSelf.currentLine = -1;
        
        // update the status item
        [strongSelf cycleLines];
        
        // sort out multi-line cycler
        if (strongSelf.isMultiline) {
          
          // start the timer to keep cycling lines
          strongSelf.lineCycleTimer = [NSTimer scheduledTimerWithTimeInterval:strongSelf.cycleLinesIntervalSeconds target:strongSelf selector:@selector(cycleLines) userInfo:nil repeats:YES];
          
        }
        
        // tell the manager this plugin has updated
        [strongSelf.manager pluginDidUdpdateItself:strongSelf];
        
        // strongSelf next refresh
        strongSelf.refreshTimer = [NSTimer scheduledTimerWithTimeInterval:[strongSelf.refreshIntervalSeconds doubleValue] target:strongSelf selector:@selector(refresh) userInfo:nil repeats:NO];
      }

    });
  });

  return YES;
}

- (void) close {
  [self.lineCycleTimer invalidate];
  self.lineCycleTimer = nil;
  [self.refreshTimer invalidate];
  self.refreshTimer = nil;
}


- (void) copyOutput {

  NSString *valueToCopy = [self.allContentLines objectAtIndex:self.currentLine];
  NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
  [pasteboard clearContents];
  [pasteboard writeObjects:[NSArray arrayWithObject:valueToCopy]];

}

- (void) copyAllOutput {

  NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
  [pasteboard clearContents];
  [pasteboard writeObjects:[NSArray arrayWithObject:self.allContent]];

}

- (void) runPluginExternally {

  NSString* script = @"tell application \"Terminal\" \n\
  do script \"%@\" \n\
  activate \n\
  end tell";

  NSString *s = [NSString stringWithFormat:
                 script, [self.path stringByReplacingOccurrencesOfString:@" " withString:@"\\\\ "]];
  NSAppleScript *as = [NSAppleScript.alloc initWithSource:s];
  [as executeAndReturnError:nil];

}

- (void) addAdditionalMenuItems:(NSMenu *)menu {

  if (!DEFS.userConfigDisabled) {
    NSMenuItem *runItem = [NSMenuItem.alloc initWithTitle:@"Run in Terminal…" action:@selector(runPluginExternally) keyEquivalent:@"o"];
    [runItem setTarget:self];
    [menu addItem:runItem];
  }

}

@end


================================================
FILE: archive/bitbar/App/BitBar/HTMLPlugin.h
================================================
//
//  HTMLPlugin.h
//  BitBar
//
//  Created by Mathias Leppich on 22/01/14.
//  Copyright (c) 2014 Bit Bar. All rights reserved.
//

#import "Plugin.h"

@class WebView;

@interface HTMLPlugin : Plugin

@property (nonatomic, strong) NSTimer *autoReloadTimer;
@property (nonatomic, strong) WebView *webView;

@property (nonatomic, readonly) NSString * reloadInterval;

@property (nonatomic, strong) NSMenu * menu;


// callable from JavaScript
- (void) resizeToFit;
- (void) resetMenu;
- (void) addMenuItem:(NSObject*)titleOrParamsDict;
- (void) addMenuItems:(NSObject*)titleOrParamsDict;
- (void) addMenuSeperatorItem;
- (void) showMenu;
- (void) showWebInspector;

@end


================================================
FILE: archive/bitbar/App/BitBar/HTMLPlugin.m
================================================
//
//  HTMLPlugin.m
//  BitBar
//
//  Created by Mathias Leppich on 22/01/14.
//  Copyright (c) 2014 Bit Bar. All rights reserved.
//

#import "HTMLPlugin.h"
#import "PluginManager.h"
#import <WebKit/WebKit.h>

@interface WebInspector : NSObject  { WebView *_webView; }
- (id)initWithWebView:(WebView *)webView;
- (void)detach:     (id)sender;
- (void)show:       (id)sender;
- (void)showConsole:(id)sender;
@end

@implementation HTMLPlugin

-(BOOL)refresh {
  
  if (![[NSFileManager defaultManager] fileExistsAtPath:self.path]) {
    return NO;
  }
  
  NSLog(@" HTML File: %@", self.path);
  self.content = @"HTML File";
  self.currentLine = -1;
  [self cycleLines];
  
  [self.manager pluginDidUdpdateItself:self];
  [self reinitAutoReloadTimer];
  return YES;
}

-(void)reinitAutoReloadTimer {
  [self.autoReloadTimer invalidate];
  self.autoReloadTimer = [NSTimer scheduledTimerWithTimeInterval:[self.refreshIntervalSeconds doubleValue]
                                                          target:self
                                                        selector:@selector(reloadWebView)
                                                        userInfo:nil
                                                         repeats:YES];
  
}

-(void)reloadWebView {
  NSLog(@"soft reload");
  [self.webView reload:nil];
}

-(void)rebuildMenuForStatusItem:(NSStatusItem *)statusItem {
  WebView * webview = [WebView.alloc initWithFrame:NSMakeRect(0, 0, 15, 15)];

  self.webView = webview;
  
  webview.frameLoadDelegate = (id)self;
  webview.resourceLoadDelegate = (id)self;
  webview.UIDelegate = (id)self;
  webview.drawsBackground = NO;
  webview.mainFrame.frameView.allowsScrolling = NO;
  webview.shouldUpdateWhileOffscreen = YES;
  webview.autoresizingMask = NSViewWidthSizable;

  NSURL * url = [NSURL fileURLWithPath:self.path];
  NSURLRequest * req = [NSURLRequest.alloc initWithURL:url];
  [webview.mainFrame loadRequest:req];
  statusItem.view = webview;

  [self resetMenu];
}

- (void)resizeWebViewToFitContents {
  WebView * webView = (WebView *)self.statusItem.view;
  WebFrame * webFrame = [webView mainFrame];
  
  //get the rect for the rendered frame
  NSRect webFrameRect = [[[webFrame frameView] documentView] frame];
  //get the rect of the current webview
  NSRect webViewRect = [webView frame];
  
  //calculate the new frame
  NSRect newWebViewRect = NSMakeRect(webViewRect.origin.x,
                                     webViewRect.origin.y,
                                     webFrameRect.size.width,
                                     webViewRect.size.height);
  //set the frame
  [webView setFrame:newWebViewRect];
  NSLog(@"The dimensions of the page are: %@",NSStringFromRect(webFrameRect));
}

# pragma mark Delegate methods

- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)webFrame
{
  [self resizeWebViewToFitContents];
}

- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame
{
  // WebInspector * inspector = [WebInspector.alloc initWithWebView:sender];
  // [inspector detach:sender];
  // [inspector showConsole:sender];

	NSLog(@"didClearWindowObject: windowObject: %@", windowObject);
	WebScriptObject * script = [sender windowScriptObject];
	NSLog(@"didClearWindowObject: script: %@", script);
	[script setValue:self forKey:@"BitBar"];
  [script evaluateWebScript:@"Object.isArray = function(a){ return a instanceof Array; }"];
}

- (void)webView:(WebView *)webView addMessageToConsole:(NSDictionary *)dictionary
{
	NSLog(@"Error from webkit: %@", dictionary);
}

-(NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems {
  // disable right-click menu in webview
  return nil;
}

-(BOOL)webView:(WebView *)sender shouldChangeSelectedDOMRange:(DOMRange *)currentRange toDOMRange:(DOMRange *)proposedRange affinity:(NSSelectionAffinity)selectionAffinity stillSelecting:(BOOL)flag {
  // disable text selection
  return NO;
}

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector {
  SEL allowed[] = {
    @selector(log:),
    @selector(setReloadInterval:),
    @selector(resizeToFit),
    @selector(showWebInspector),
    @selector(resetMenu),
    @selector(addMenuItem:),
    @selector(addMenuItems:),
    @selector(addMenuSeperatorItem),
    @selector(showMenu)
  };
  for (int i=0; i<(sizeof allowed)/(sizeof allowed[0]); i++) {
    if (allowed[i] == selector) {
      NSLog(@"allow Selector: %@", NSStringFromSelector(selector));
      return NO;
    }
  }
  //NSLog(@"isSelectorExcludedFromWebScript: %@", NSStringFromSelector(selector));
  return YES;
}

+(NSString *)webScriptNameForSelector:(SEL)selector {
  return [NSStringFromSelector(selector) stringByReplacingOccurrencesOfString:@":" withString:@""];
}

+(BOOL)isKeyExcludedFromWebScript:(const char *)name {
  NSArray * allowed = @[
                        @"reloadInterval"
                        ];
  if ([allowed containsObject:[NSString stringWithUTF8String:name]]) {
    NSLog(@"isKeyExcludedFromWebScript: %@", [NSString stringWithUTF8String:name]);
    return NO;
  }
  return YES;
}

#pragma mark - WebScriptObject Utils

- (NSArray*) arrayOfKeysFromWebScriptObject:(WebScriptObject *)obj {
  WebScriptObject* bridge = [obj evaluateWebScript:@"Object"];
  WebScriptObject* keysObj = [bridge callWebScriptMethod:@"keys" withArguments:@[obj]];
  return [self arrayFromWebScriptObject:keysObj];
}

- (NSDictionary*) dictionaryFromWebScriptObject:(WebScriptObject *)obj {
  NSArray * keys = [self arrayOfKeysFromWebScriptObject:obj];
  NSMutableDictionary * dict = [NSMutableDictionary.alloc initWithCapacity:keys.count];
  for (NSString * key in keys) {
    NSObject * value = [obj valueForKey:key];
    if ([[value class] isSubclassOfClass:[NSString class]] || [[value class] isSubclassOfClass:[NSNumber class]]) {
      [dict setObject:value forKey:key];
    }
  }
  return dict;
}

- (BOOL) isWebScriptObjectInstanceOfArray:(WebScriptObject *)obj {
  WebScriptObject* bridge = [obj evaluateWebScript:@"Object"];
  NSNumber * result = [bridge callWebScriptMethod:@"isArray" withArguments:@[obj]];
  return [result boolValue];
}

- (NSArray*) arrayFromWebScriptObject:(WebScriptObject *)obj {
  NSMutableArray * values = NSMutableArray.new;
  id elem = nil;
  int i = 0;
  WebUndefined *undefined = [WebUndefined undefined];
  while ((elem = [obj webScriptValueAtIndex:i++]) != undefined) {
    [values addObject:elem];
  }
  return values;
}


#pragma mark - Called from JavaScript

-(void)log:(NSString*) str {
  NSLog(@"JAVASCRIPT LOG: %@", str);
}

-(NSNumber *)reloadInterval {
  NSLog(@"reloadInterval:");
  return self.refreshIntervalSeconds;
}

-(void)setReloadInterval:(NSNumber* )arg{
  if (![arg isKindOfClass:[NSNumber class]]) {
    return;
  }
  NSLog(@"setReloadInterval: %@", arg);
  self.refreshIntervalSeconds = arg;
  [self reinitAutoReloadTimer];
}

- (void) resizeToFit {
  [self resizeWebViewToFitContents];
}

- (void) resetMenu {
  NSLog(@"resetMenu");
  _menu = NSMenu.new;
}

- (void) addMenuItem:(NSObject*)titleOrParamsDict {
  [self addMenuItems:titleOrParamsDict];
}

- (void) addMenuItems:(NSObject*)titleOrParamsDict {
  NSDictionary * params = nil;
  if ([[titleOrParamsDict class] isSubclassOfClass:[NSString class]]) {
    NSString * title = (NSString *)titleOrParamsDict;
    if ([title hasPrefix:@"---"]) {
      [self addMenuSeperatorItem];
      return;
    }
    params = @{@"title": title};
  }
  else if ([[titleOrParamsDict class] isSubclassOfClass:[NSArray class]]) {
    NSArray * values = (NSArray *)titleOrParamsDict;
    for (NSObject * value in values) {
      [self addMenuItem:value];
    }
    return;
  }
  else if ([[titleOrParamsDict class] isSubclassOfClass:[NSDictionary class]]) {
    params = (NSDictionary *)titleOrParamsDict;
  }
  else if ([[titleOrParamsDict class] isSubclassOfClass:[WebScriptObject class]]) {
    WebScriptObject * obj = (WebScriptObject*) titleOrParamsDict;
    if ([self isWebScriptObjectInstanceOfArray:obj]) {
      [self addMenuItems:[self arrayFromWebScriptObject:obj]];
      return;
    } else {
      params = [self dictionaryFromWebScriptObject:obj];
    }
  }
  else {
    NSLog(@"addMenuItem: ERROR: unhandled class: %@", [titleOrParamsDict class]);
  }
  if (params != nil) {
    NSLog(@"addMenuItem: %@", params);
    NSMenuItem * item = [self buildMenuItemWithParams:params];
    [_menu addItem:item];
  }
}

- (void) addMenuSeperatorItem {
  NSLog(@"addMenuSeperatorItem");
  [_menu addItem:[NSMenuItem separatorItem]];
}

- (void) showMenu {
  NSLog(@"showMenu");
  _menu.delegate = self;
  
  [self addDefaultMenuItems:_menu];
  self.statusItem.menu = _menu;
  [self.statusItem popUpStatusItemMenu:self.statusItem.menu];
}

- (void) showWebInspector {
  WebView * webview = (WebView*) self.statusItem.view;
  WebInspector * inspector = [WebInspector.alloc initWithWebView:webview];
  // [inspector detach:sender];
  [inspector showConsole:webview];
}

@end


================================================
FILE: archive/bitbar/App/BitBar/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "size" : "16x16",
      "idiom" : "mac",
      "filename" : "bitbar-16.png",
      "scale" : "1x"
    },
    {
      "size" : "16x16",
      "idiom" : "mac",
      "filename" : "bitbar-32.png",
      "scale" : "2x"
    },
    {
      "size" : "32x32",
      "idiom" : "mac",
      "filename" : "bitbar-33.png",
      "scale" : "1x"
    },
    {
      "size" : "32x32",
      "idiom" : "mac",
      "filename" : "bitbar-64.png",
      "scale" : "2x"
    },
    {
      "size" : "128x128",
      "idiom" : "mac",
      "filename" : "bitbar-128.png",
      "scale" : "1x"
    },
    {
      "size" : "128x128",
      "idiom" : "mac",
      "filename" : "bitbar-256.png",
      "scale" : "2x"
    },
    {
      "size" : "256x256",
      "idiom" : "mac",
      "filename" : "bitbar-257.png",
      "scale" : "1x"
    },
    {
      "size" : "256x256",
      "idiom" : "mac",
      "filename" : "bitbar-512.png",
      "scale" : "2x"
    },
    {
      "size" : "512x512",
      "idiom" : "mac",
      "filename" : "bitbar-513.png",
      "scale" : "1x"
    },
    {
      "size" : "512x512",
      "idiom" : "mac",
      "filename" : "bitbar-1024.png",
      "scale" : "2x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: archive/bitbar/App/BitBar/NSColor+Hex.h
================================================
//
//  NSColor+Hex.h
//  BitBar
//
//  Created by Mathias Leppich on 03/02/14.
//  Copyright (c) 2014 Bit Bar. All rights reserved.
//

#import <Cocoa/Cocoa.h>

@interface NSColor (Hex)

+ (NSColor*) colorWithWebColorString:(NSString*)color;
+ (NSColor*) colorWithHexColorString:(NSString*)hex;

@end


================================================
FILE: archive/bitbar/App/BitBar/NSColor+Hex.m
================================================
//
//  NSColor+Hex.m
//  BitBar
//
//  Created by Mathias Leppich on 03/02/14.
//  Copyright (c) 2014 Bit Bar. All rights reserved.
//

#import "NSColor+Hex.h"

@implementation NSColor (Hex)

+ (NSDictionary *)cssColors {

  static NSDictionary *cssDictionary = nil;


  return cssDictionary = cssDictionary ?: @{@"lightseagreen":@"20b2aa", @"floralwhite":@"fffaf0", @"lightgray":@"d3d3d3", @"darkgoldenrod":@"b8860b", @"paleturquoise":@"afeeee", @"goldenrod":@"daa520", @"skyblue":@"87ceeb", @"indianred":@"cd5c5c", @"darkgray":@"a9a9a9", @"khaki":@"f0e68c", @"blue":@"0000ff", @"darkred":@"8b0000", @"lightyellow":@"ffffe0", @"midnightblue":@"191970", @"chartreuse":@"7fff00", @"lightsteelblue":@"b0c4de", @"slateblue":@"6a5acd", @"firebrick":@"b22222", @"moccasin":@"ffe4b5", @"salmon":@"fa8072", @"sienna":@"a0522d", @"slategray":@"708090", @"teal":@"008080", @"lightsalmon":@"ffa07a", @"pink":@"ffc0cb", @"burlywood":@"deb887", @"gold":@"ffd700", @"springgreen":@"00ff7f", @"lightcoral":@"f08080", @"black":@"000000", @"blueviolet":@"8a2be2", @"chocolate":@"d2691e", @"aqua":@"00ffff", @"darkviolet":@"9400d3", @"indigo":@"4b0082", @"darkcyan":@"008b8b", @"orange":@"ffa500", @"antiquewhite":@"faebd7", @"peru":@"cd853f", @"silver":@"c0c0c0", @"purple":@"800080", @"saddlebrown":@"8b4513", @"lawngreen":@"7cfc00", @"dodgerblue":@"1e90ff", @"lime":@"00ff00", @"linen":@"faf0e6", @"lightblue":@"add8e6", @"darkslategray":@"2f4f4f", @"lightskyblue":@"87cefa", @"mintcream":@"f5fffa", @"olive":@"808000", @"hotpink":@"ff69b4", @"papayawhip":@"ffefd5", @"mediumseagreen":@"3cb371", @"mediumspringgreen":@"00fa9a", @"cornflowerblue":@"6495ed", @"plum":@"dda0dd", @"seagreen":@"2e8b57", @"palevioletred":@"db7093", @"bisque":@"ffe4c4", @"beige":@"f5f5dc", @"darkorchid":@"9932cc", @"royalblue":@"4169e1", @"darkolivegreen":@"556b2f", @"darkmagenta":@"8b008b", @"orange red":@"ff4500", @"lavender":@"e6e6fa", @"fuchsia":@"ff00ff", @"darkseagreen":@"8fbc8f", @"lavenderblush":@"fff0f5", @"wheat":@"f5deb3", @"steelblue":@"4682b4", @"lightgoldenrodyellow":@"fafad2", @"lightcyan":@"e0ffff", @"mediumaquamarine":@"66cdaa", @"turquoise":@"40e0d0", @"dark blue":@"00008b", @"darkorange":@"ff8c00", @"brown":@"a52a2a", @"dimgray":@"696969", @"deeppink":@"ff1493", @"powderblue":@"b0e0e6", @"red":@"ff0000", @"darkgreen":@"006400", @"ghostwhite":@"f8f8ff", @"white":@"ffffff", @"navajowhite":@"ffdead", @"navy":@"000080", @"ivory":@"fffff0", @"palegreen":@"98fb98", @"whitesmoke":@"f5f5f5", @"gainsboro":@"dcdcdc", @"mediumslateblue":@"7b68ee", @"olivedrab":@"6b8e23", @"mediumpurple":@"9370db", @"darkslateblue":@"483d8b", @"blanchedalmond":@"ffebcd", @"darkkhaki":@"bdb76b", @"green":@"008000", @"limegreen":@"32cd32", @"snow":@"fffafa", @"tomato":@"ff6347", @"darkturquoise":@"00ced1", @"orchid":@"da70d6", @"yellow":@"ffff00", @"green yellow":@"adff2f", @"azure":@"f0ffff", @"mistyrose":@"ffe4e1", @"cadetblue":@"5f9ea0", @"oldlace":@"fdf5e6", @"gray":@"808080", @"honeydew":@"f0fff0", @"peachpuff":@"ffdab9", @"tan":@"d2b48c", @"thistle":@"d8bfd8", @"palegoldenrod":@"eee8aa", @"mediumorchid":@"ba55d3", @"rosybrown":@"bc8f8f", @"mediumturquoise":@"48d1cc", @"lemonchiffon":@"fffacd", @"maroon":@"800000", @"mediumvioletred":@"c71585", @"violet":@"ee82ee", @"yellow green":@"9acd32", @"coral":@"ff7f50", @"lightgreen":@"90ee90", @"cornsilk":@"fff8dc", @"mediumblue":@"0000cd", @"aliceblue":@"f0f8ff", @"forestgreen":@"228b22", @"aquamarine":@"7fffd4", @"deepskyblue":@"00bfff", @"lightslategray":@"778899", @"darksalmon":@"e9967a", @"crimson":@"dc143c", @"sandybrown":@"f4a460", @"lightpink":@"ffb6c1", @"seashell":@"fff5ee"};
}

+ (NSColor*)colorWithWebColorString:(NSString*)colorString
{
  NSString * hexString = [colorString hasPrefix:@"#"]
                       ? [colorString substringWithRange:NSMakeRange(1,colorString.length - 1)]
                       : self.cssColors[colorString.lowercaseString];
  return [self colorWithHexColorString:hexString];
}

+ (NSColor*)colorWithHexColorString:(NSString*)inColorString
{
  NSColor* result = nil;
  unsigned colorCode = 0;
  unsigned char redByte, greenByte, blueByte;
  
  if (nil != inColorString)
  {
    NSScanner* scanner = [NSScanner scannerWithString:inColorString];
    (void) [scanner scanHexInt:&colorCode]; // ignore error
  }
  redByte = (unsigned char)(colorCode >> 16);
  greenByte = (unsigned char)(colorCode >> 8);
  blueByte = (unsigned char)(colorCode); // masks off high bits
  
  result = [NSColor
            colorWithCalibratedRed:(CGFloat)redByte / 0xff
            green:(CGFloat)greenByte / 0xff
            blue:(CGFloat)blueByte / 0xff
            alpha:1.0];
  return result;
}

@end


================================================
FILE: archive/bitbar/App/BitBar/NSString+ANSI.h
================================================
//
//  NSString+ANSI.h
//  BitBar
//
//  Created by Kent Karlsson on 3/11/16.
//  Copyright © 2016 Bit Bar. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSString (ANSI)

- (BOOL)containsANSICodes;
- (NSMutableAttributedString*)attributedStringParsingANSICodes;

@end


================================================
FILE: archive/bitbar/App/BitBar/NSString+ANSI.m
================================================
//
//  NSString+ANSI.m
//  BitBar
//
//  Created by Kent Karlsson on 3/11/16.
//  Copyright © 2016 Bit Bar. All rights reserved.
//

#import "Cocoa/Cocoa.h"
#import "NSString+ANSI.h"
#import "NSColor+Hex.h"

@implementation NSMutableDictionary (ANSI)

- (NSMutableDictionary*)modifyAttributesForANSICodes:(NSString*)codes {
  BOOL bold = NO;
  NSFont* font = self[NSFontAttributeName];

  NSArray* codeArray = [codes componentsSeparatedByString:@";"];

  for (NSString* codeString in codeArray) {
    int code = codeString.intValue;
    switch (code) {
      case 0:
        [self removeAllObjects];
        // remove italic and bold from font here
        if (font) self[NSFontAttributeName] = font;
        break;

      case 1:
      case 22:
        bold = (code == 1);
        break;

    // case 3: italic
    // case 23: italic off
    // case 4: underlined
    // case 24: underlined off

      case 30:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"7f7f7f" : @"000000"];
        break;
      case 31:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"cd0000" : @"ff0000"];
        break;
      case 32:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"00cd00" : @"00ff00"];
        break;
      case 33:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"cdcd00" : @"ffff00"];
        break;
      case 34:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"0000ee" : @"5c5cff"];
        break;
      case 35:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"cd00cd" : @"ff00ff"];
        break;
      case 36:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"00cdcd" : @"00ffff"];
        break;
      case 37:
        self[NSForegroundColorAttributeName] = [NSColor colorWithHexColorString:bold ? @"e5e5e5" : @"ffffff"];
        break;

      case 39:
        [self removeObjectForKey:NSForegroundColorAttributeName];
        break;

      case 40:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"7f7f7f"];
        break;
      case 41:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"cd0000"];
        break;
      case 42:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"00cd00"];
        break;
      case 43:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"cdcd00"];
        break;
      case 44:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"0000ee"];
        break;
      case 45:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"cd00cd"];
        break;
      case 46:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"00cdcd"];
        break;
      case 47:
        self[NSBackgroundColorAttributeName] = [NSColor colorWithHexColorString:@"e5e5e5"];
        break;

      case 49:
        [self removeObjectForKey:NSBackgroundColorAttributeName];
        break;

      default:
        break;
    }
  }

  return self;
}

@end

@implementation NSString (ANSI)

- (BOOL)containsANSICodes {
  return [self rangeOfString:@"\033["].location != NSNotFound;
}

- (NSMutableAttributedString*)attributedStringParsingANSICodes {
  NSMutableAttributedString* result = [[NSMutableAttributedString alloc] init];

  NSMutableDictionary* attributes = [NSMutableDictionary.alloc init];
  NSArray* parts = [self componentsSeparatedByString:@"\033["];
  [result appendAttributedString:[NSAttributedString.alloc initWithString:parts.firstObject attributes:nil]];
  
  for (NSString* part in [parts subarrayWithRange:NSMakeRange(1, parts.count - 1)]) {
    if (part.length == 0)
      continue;

    NSArray* sequence = [part componentsSeparatedByString:@"m"];
    NSString* text = sequence.lastObject;

    if (sequence.count < 2) {
      [result appendAttributedString:[NSAttributedString.alloc initWithString:text attributes:attributes]];
    } else if (sequence.count >= 2) {
      text = [[sequence subarrayWithRange:NSMakeRange(1, sequence.count - 1)] componentsJoinedByString:@"m"];
      [attributes modifyAttributesForANSICodes:sequence[0]];
      [result appendAttributedString:[NSAttributedString.alloc initWithString:text attributes:attributes]];
    }
  }

  return result;
}

@end


================================================
FILE: archive/bitbar/App/BitBar/NSUserDefaults+Settings.h
================================================
//
//  Settings.h
//  BitBar
//
//  Created by Mat Ryer on 11/13/13.
//  Copyright (c) 2013 Bit Bar. All rights reserved.
//

@import Foundation;

@interface NSUserDefaults (Settings)

@property NSString* pluginsDirectory;
@property BOOL isFirstTimeAppRun;
@property BOOL userConfigDisabled;

@end

#define DEFS NSUserDefaults.standardUserDefaults


================================================
FILE: archive/bitbar/App/BitBar/NSUserDefaults+Settings.m
================================================
//
//  Settings.m
//  BitBar
//
//  Created by Mat Ryer on 11/13/13.
//  Copyright (c) 2013 Bit Bar. All rights reserved.
//

#import "NSUserDefaults+Settings.h"

@implementation NSUserDefaults (Settings)

- (NSString *)pluginsDirectory {
#ifdef DISTRO
  return [self stringForKey:@"pluginsDirectory"] ?: [NSBundle mainBundle].executablePath.stringByDeletingLastPathComponent;
#else
  return [self stringForKey:@"pluginsDirectory"];
#endif
}
- (void) setPluginsDirectory:(NSString*)value {

  [self setObject:value forKey:@"pluginsDirectory"];
}

- (BOOL) isFirstTimeAppRun { return ![self boolForKey:@"appHasRun"]; }

- (void) setIsFirstTimeAppRun:(BOOL)firstTime {
  [self setBool:!firstTime forKey:@"appHasRun"];
}

- (BOOL)userConfigDisabled {
#ifdef DISTRO
  id disabled = [self objectForKey:@"userConfigDisabled"];
  return disabled ? [disabled boolValue] : YES;
#else
  return [self boolForKey:@"userConfigDisabled"];
#endif
}

- (void)setUserConfigDisabled:(BOOL)disabled {
  [self setBool:disabled forKey:@"userConfigDisabled"];
}

@end



================================================
FILE: archive/bitbar/App/BitBar/Plugin.h
================================================
//
//  Plugin.h
//  BitBar
//
//  Created by Mat Ryer on 11/12/13.
//  Copyright (c) 2013 Bit Bar. All rights reserved.
//

@import AppKit;
@class PluginManager;

@interface Plugin : NSObject <NSMenuDelegate>

@property (nonatomic)      NSInteger currentLine, cycleLinesIntervalSeconds;
@property (nonatomic)           BOOL lastCommandWasError, pluginIsVisible, menuIsOpen;
@property (readonly)            BOOL isMultiline;
@property (readonly)        NSString *lastUpdatedString;
@property (nonatomic, copy) NSString *path, *name, *content, *allContent, *errorContent;
@property (nonatomic)        NSArray *allContentLines;
@property (nonatomic)        NSArray *titleLines;
@property (nonatomic)       NSNumber *refreshIntervalSeconds;
@property (nonatomic)     NSMenuItem *lastUpdatedMenuItem;
@property (nonatomic)         NSDate *lastUpdated;
@property (weak, readonly)   PluginManager *manager;

// UI
@property (nonatomic) NSStatusItem *statusItem;

- initWithManager:(PluginManager*)manager;
- (void) close;

- (NSMenuItem*) buildMenuItemForLine:(NSString *)line;
- (NSMenuItem*) buildMenuItemWithParams:(NSDictionary *)params;
- (NSDictionary *)dictionaryForLine:(NSString *)line;
- (void) rebuildMenuForStatusItem:(NSStatusItem*)statusItem;
- (void) addAdditionalMenuItems:(NSMenu *)menu;
- (void) addDefaultMenuItems:(NSMenu *)menu;

- (void)performRefreshNow;
- (BOOL) refresh;
- (void) cycleLines;
- (void) contentHasChanged;
- (BOOL) isFontValid:(NSString *)fontName;

// actions
- (void)changePluginsDirectorySelected:(id)sender;


@end


================================================
FILE: archive/bitbar/App/BitBar/Plugin.m
================================================
//
//  Plugin.m
//  BitBar
//
//  Created by Mat Ryer on 11/12/13.
//  Copyright (c) 2013 Bit Bar. All rights reserved.
//

#import "Plugin.h"
#import "PluginManager.h"
#import "STPrivilegedTask.h"
#import "NSDate+DateTools.h"
#import "NSColor+Hex.h"
#import "NSString+Emojize.h"
#import "NSString+ANSI.h"

#define DEFAULT_TIME_INTERVAL_SECONDS ((double)60.)

@implementation Plugin

- init { return (self = super.init) ? _currentLine = -1, _cycleLinesIntervalSeconds = 5, self : nil; }

- initWithManager:(PluginManager*)manager { return (self = self.init) ? _manager = manager, self : nil; }

- (NSStatusItem *)statusItem { return _statusItem = _statusItem ?: ({
    
    // make the status item
    _statusItem = [self.manager.statusBar statusItemWithLength:NSVariableStatusItemLength];
    
    // build the menu
    [self rebuildMenuForStatusItem:_statusItem]; _statusItem; });
  
}

- (NSImage*) createImageFromBase64:(NSString*)string isTemplate:(BOOL)template{
  NSData * imageData;
  if ([NSData instancesRespondToSelector:@selector(initWithBase64EncodedString:options:)]) {
    imageData = [[NSData alloc] initWithBase64EncodedString:string options:0];
  }else {
    imageData = [[NSData alloc] initWithBase64Encoding:string];
  }
  NSImage * image = [[NSImage alloc] initWithData:imageData];
  if (template) {
    [image setTemplate:true];
  }
  return image;
}

- (NSMenuItem*) buildMenuItemWithParams:(NSDictionary *)params {

  if ([[params[@"dropdown"] lowercaseString] isEqualToString:@"false"]) {
    return nil;
  }
  
  NSString * fullTitle = params[@"title"];
  if (![[params[@"emojize"] lowercaseString] isEqualToString:@"false"]) {
    fullTitle = [fullTitle emojizedString];
  }
  if (![[params[@"trim"] lowercaseString] isEqualToString:@"false"]) {
      fullTitle = [fullTitle stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
  }

  CGFloat titleLength = [fullTitle length];
  CGFloat lengthParam = params[@"length"] ? [params[@"length"] floatValue] : titleLength;
  CGFloat truncLength = lengthParam >= ti
Download .txt
gitextract_oblxmil8/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       └── deploy-xbarappcom.yaml
├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE.txt
├── README.md
├── app/
│   ├── .gitignore
│   ├── README.md
│   ├── app.go
│   ├── build.sh
│   ├── categories_service.go
│   ├── categories_service_test.go
│   ├── command_service.go
│   ├── frontend/
│   │   ├── .gitignore
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── public/
│   │   │   └── index.html
│   │   ├── rollup.config.js
│   │   ├── src/
│   │   │   ├── App.svelte
│   │   │   ├── Homepage.svelte
│   │   │   ├── InstalledPluginView.svelte
│   │   │   ├── PersonView.svelte
│   │   │   ├── PluginView.svelte
│   │   │   ├── PluginsList.svelte
│   │   │   ├── backend/
│   │   │   │   ├── index.js
│   │   │   │   └── package.json
│   │   │   ├── elements/
│   │   │   │   ├── A.svelte
│   │   │   │   ├── Breadcrumbs.svelte
│   │   │   │   ├── Button.svelte
│   │   │   │   ├── Duration.svelte
│   │   │   │   ├── Error.svelte
│   │   │   │   ├── KeyboardShortcuts.svelte
│   │   │   │   ├── PluginCollection.svelte
│   │   │   │   ├── PluginDetails.svelte
│   │   │   │   ├── PluginSourceBrowser.svelte
│   │   │   │   ├── Spinner.svelte
│   │   │   │   ├── Switch.svelte
│   │   │   │   ├── VariableInput.svelte
│   │   │   │   └── Variables.svelte
│   │   │   ├── main.js
│   │   │   ├── pagedata.svelte
│   │   │   ├── rpc.svelte
│   │   │   ├── signals.svelte
│   │   │   ├── styles.css
│   │   │   └── waiters.svelte
│   │   └── tailwind.config.js
│   ├── go.mod
│   ├── go.sum
│   ├── incoming_urls.go
│   ├── incoming_urls_test.go
│   ├── main.go
│   ├── menu_parser.go
│   ├── menu_parser_test.go
│   ├── package.json
│   ├── package.sh
│   ├── person_service.go
│   ├── plugins_service.go
│   ├── settings.go
│   ├── settings_test.go
│   ├── test.sh
│   └── wails.json
├── archive/
│   └── bitbar/
│       ├── .gitignore
│       ├── .gitmodules
│       ├── .travis.yml
│       ├── App/
│       │   ├── BitBar/
│       │   │   ├── App.xib
│       │   │   ├── AppDelegate.m
│       │   │   ├── Base.lproj/
│       │   │   │   └── MainMenu.xib
│       │   │   ├── BitBar-Info.plist
│       │   │   ├── CHANGELOG.md
│       │   │   ├── ExecutablePlugin.h
│       │   │   ├── ExecutablePlugin.m
│       │   │   ├── HTMLPlugin.h
│       │   │   ├── HTMLPlugin.m
│       │   │   ├── Images.xcassets/
│       │   │   │   └── AppIcon.appiconset/
│       │   │   │       └── Contents.json
│       │   │   ├── NSColor+Hex.h
│       │   │   ├── NSColor+Hex.m
│       │   │   ├── NSString+ANSI.h
│       │   │   ├── NSString+ANSI.m
│       │   │   ├── NSUserDefaults+Settings.h
│       │   │   ├── NSUserDefaults+Settings.m
│       │   │   ├── Plugin.h
│       │   │   ├── Plugin.m
│       │   │   ├── PluginManager.h
│       │   │   ├── PluginManager.m
│       │   │   └── incoming-url-tests.html
│       │   ├── BitBar.xcodeproj/
│       │   │   ├── project.pbxproj
│       │   │   ├── project.xcworkspace/
│       │   │   │   ├── contents.xcworkspacedata
│       │   │   │   └── xcshareddata/
│       │   │   │       ├── BitBar.xccheckout
│       │   │   │       ├── BitBar.xcscmblueprint
│       │   │   │       └── IDEWorkspaceChecks.plist
│       │   │   └── xcshareddata/
│       │   │       └── xcschemes/
│       │   │           ├── BitBar.xcscheme
│       │   │           └── BitBarDistro.xcscheme
│       │   ├── BitBarTests/
│       │   │   ├── BitBarTests-Info.plist
│       │   │   ├── PluginManager+Test.h
│       │   │   ├── PluginManager+Test.m
│       │   │   ├── PluginManagerTest.m
│       │   │   ├── PluginTest.m
│       │   │   └── TestPlugins/
│       │   │       ├── one.10s.sh
│       │   │       ├── three.7d.sh
│       │   │       └── two.5m.sh
│       │   └── Vendor/
│       │       ├── AHProxySettings/
│       │       │   ├── .gitignore
│       │       │   ├── AHProxyExampe/
│       │       │   │   ├── AppDelegate.h
│       │       │   │   ├── AppDelegate.m
│       │       │   │   ├── Base.lproj/
│       │       │   │   │   └── MainMenu.xib
│       │       │   │   ├── Images.xcassets/
│       │       │   │   │   └── AppIcon.appiconset/
│       │       │   │   │       └── Contents.json
│       │       │   │   ├── Info.plist
│       │       │   │   └── main.m
│       │       │   ├── AHProxySettings/
│       │       │   │   ├── AHProxy.h
│       │       │   │   ├── AHProxy.m
│       │       │   │   ├── AHProxySettings.h
│       │       │   │   ├── AHProxySettings.m
│       │       │   │   ├── NSTask+useSystemProxies.h
│       │       │   │   └── NSTask+useSystemProxies.m
│       │       │   ├── AHProxySettings.podspec
│       │       │   ├── AHProxySettings.xcodeproj/
│       │       │   │   ├── project.pbxproj
│       │       │   │   ├── project.xcworkspace/
│       │       │   │   │   └── contents.xcworkspacedata
│       │       │   │   └── xcshareddata/
│       │       │   │       └── xcschemes/
│       │       │   │           └── AHProxySettings.xcscheme
│       │       │   ├── AHProxySettingsTests/
│       │       │   │   ├── AHProxySettingsTest.m
│       │       │   │   └── Info.plist
│       │       │   ├── LICENSE
│       │       │   └── README.md
│       │       ├── DateTools/
│       │       │   ├── .gitignore
│       │       │   ├── .travis.yml
│       │       │   ├── CREDITS.md
│       │       │   ├── DateTools/
│       │       │   │   ├── DTConstants.h
│       │       │   │   ├── DTConstants.m
│       │       │   │   ├── DTError.h
│       │       │   │   ├── DTError.m
│       │       │   │   ├── DTTimePeriod.h
│       │       │   │   ├── DTTimePeriod.m
│       │       │   │   ├── DTTimePeriodChain.h
│       │       │   │   ├── DTTimePeriodChain.m
│       │       │   │   ├── DTTimePeriodCollection.h
│       │       │   │   ├── DTTimePeriodCollection.m
│       │       │   │   ├── DTTimePeriodGroup.h
│       │       │   │   ├── DTTimePeriodGroup.m
│       │       │   │   ├── DateTools.bundle/
│       │       │   │   │   ├── ar.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── bg.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ca.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── cs.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── cy.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── da.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── de.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── en.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── es.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── eu.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── fi.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── fr.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── gre.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── gu.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── he.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── hi.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── hr.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── hu.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── id.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── is.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── it.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ja.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ko.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── lv.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ms.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── nb.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── nl.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── pl.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── pt-PT.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── pt.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ro.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── ru.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── sl.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── sv.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── th.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── tr.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── uk.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── vi.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   ├── zh-Hans.lproj/
│       │       │   │   │   │   └── DateTools.strings
│       │       │   │   │   └── zh-Hant.lproj/
│       │       │   │   │       └── DateTools.strings
│       │       │   │   ├── DateTools.h
│       │       │   │   ├── NSDate+DateTools.h
│       │       │   │   └── NSDate+DateTools.m
│       │       │   ├── DateTools.podspec
│       │       │   ├── Examples/
│       │       │   │   └── DateToolsExample/
│       │       │   │       ├── DateTools/
│       │       │   │       │   └── Info.plist
│       │       │   │       ├── DateToolsExample/
│       │       │   │       │   ├── AppDelegate.h
│       │       │   │       │   ├── AppDelegate.m
│       │       │   │       │   ├── Colours.h
│       │       │   │       │   ├── Colours.m
│       │       │   │       │   ├── DateToolsExample-Info.plist
│       │       │   │       │   ├── DateToolsExample-Prefix.pch
│       │       │   │       │   ├── DateToolsViewController.h
│       │       │   │       │   ├── DateToolsViewController.m
│       │       │   │       │   ├── DateToolsViewController.xib
│       │       │   │       │   ├── ExampleNavigationController.h
│       │       │   │       │   ├── ExampleNavigationController.m
│       │       │   │       │   ├── Images.xcassets/
│       │       │   │       │   │   ├── AppIcon.appiconset/
│       │       │   │       │   │   │   └── Contents.json
│       │       │   │       │   │   └── LaunchImage.launchimage/
│       │       │   │       │   │       └── Contents.json
│       │       │   │       │   ├── TimePeriodsViewController.h
│       │       │   │       │   ├── TimePeriodsViewController.m
│       │       │   │       │   ├── TimePeriodsViewController.xib
│       │       │   │       │   ├── en.lproj/
│       │       │   │       │   │   └── InfoPlist.strings
│       │       │   │       │   └── main.m
│       │       │   │       ├── DateToolsExample.xcodeproj/
│       │       │   │       │   ├── project.pbxproj
│       │       │   │       │   ├── project.xcworkspace/
│       │       │   │       │   │   └── contents.xcworkspacedata
│       │       │   │       │   └── xcshareddata/
│       │       │   │       │       └── xcschemes/
│       │       │   │       │           └── DateTools.xcscheme
│       │       │   │       └── DateToolsExampleTests/
│       │       │   │           ├── DateToolsExampleTests-Info.plist
│       │       │   │           └── en.lproj/
│       │       │   │               └── InfoPlist.strings
│       │       │   ├── LICENSE
│       │       │   ├── README.md
│       │       │   └── Tests/
│       │       │       └── DateToolsTests/
│       │       │           ├── DateToolsTests/
│       │       │           │   ├── AppDelegate.h
│       │       │           │   ├── AppDelegate.m
│       │       │           │   ├── Base.lproj/
│       │       │           │   │   └── Main.storyboard
│       │       │           │   ├── DateToolsTests-Info.plist
│       │       │           │   ├── DateToolsTests-Prefix.pch
│       │       │           │   ├── Images.xcassets/
│       │       │           │   │   ├── AppIcon.appiconset/
│       │       │           │   │   │   └── Contents.json
│       │       │           │   │   └── LaunchImage.launchimage/
│       │       │           │   │       └── Contents.json
│       │       │           │   ├── ViewController.h
│       │       │           │   ├── ViewController.m
│       │       │           │   ├── en.lproj/
│       │       │           │   │   └── InfoPlist.strings
│       │       │           │   ├── es.lproj/
│       │       │           │   │   ├── InfoPlist.strings
│       │       │           │   │   └── Main.strings
│       │       │           │   ├── ja.lproj/
│       │       │           │   │   ├── InfoPlist.strings
│       │       │           │   │   └── Main.strings
│       │       │           │   └── main.m
│       │       │           ├── DateToolsTests.xcodeproj/
│       │       │           │   ├── project.pbxproj
│       │       │           │   ├── project.xcworkspace/
│       │       │           │   │   └── contents.xcworkspacedata
│       │       │           │   └── xcshareddata/
│       │       │           │       └── xcschemes/
│       │       │           │           ├── DateToolsTests.xcscheme
│       │       │           │           └── DateToolsTestsTests.xcscheme
│       │       │           └── DateToolsTestsTests/
│       │       │               ├── DTTimeAgoTests.m
│       │       │               ├── DTTimePeriodChainTests.m
│       │       │               ├── DTTimePeriodCollectionTests.m
│       │       │               ├── DTTimePeriodGroupTests.m
│       │       │               ├── DTTimePeriodTests.m
│       │       │               ├── DateToolsTests.m
│       │       │               ├── DateToolsTestsTests-Info.plist
│       │       │               ├── en.lproj/
│       │       │               │   └── InfoPlist.strings
│       │       │               ├── es.lproj/
│       │       │               │   └── InfoPlist.strings
│       │       │               └── ja.lproj/
│       │       │                   └── InfoPlist.strings
│       │       ├── LaunchAtLoginController/
│       │       │   ├── LaunchAtLoginController.h
│       │       │   ├── LaunchAtLoginController.m
│       │       │   └── README.md
│       │       ├── NSStringEmojize/
│       │       │   ├── .gitignore
│       │       │   ├── Example/
│       │       │   │   ├── NSStringEmojize/
│       │       │   │   │   ├── DIYAppDelegate.h
│       │       │   │   │   ├── DIYAppDelegate.m
│       │       │   │   │   ├── DIYViewController.h
│       │       │   │   │   ├── DIYViewController.m
│       │       │   │   │   ├── NSStringEmojize-Info.plist
│       │       │   │   │   ├── NSStringEmojize-Prefix.pch
│       │       │   │   │   ├── en.lproj/
│       │       │   │   │   │   └── InfoPlist.strings
│       │       │   │   │   └── main.m
│       │       │   │   ├── NSStringEmojize.xcodeproj/
│       │       │   │   │   ├── project.pbxproj
│       │       │   │   │   └── project.xcworkspace/
│       │       │   │   │       └── contents.xcworkspacedata
│       │       │   │   └── NSStringEmojizeTests/
│       │       │   │       ├── NSStringEmojizeTests-Info.plist
│       │       │   │       ├── NSStringEmojizeTests.h
│       │       │   │       ├── NSStringEmojizeTests.m
│       │       │   │       └── en.lproj/
│       │       │   │           └── InfoPlist.strings
│       │       │   ├── LICENSE.md
│       │       │   ├── NSStringEmojize/
│       │       │   │   ├── NSString+Emojize.h
│       │       │   │   ├── NSString+Emojize.m
│       │       │   │   └── emojis.h
│       │       │   ├── NSStringEmojize.podspec
│       │       │   └── README.md
│       │       ├── STPrivilegedTask/
│       │       │   ├── .gitignore
│       │       │   ├── LICENSE.txt
│       │       │   ├── PrivilegedTaskExample/
│       │       │   │   ├── PrivilegedTaskExample/
│       │       │   │   │   ├── AppDelegate.h
│       │       │   │   │   ├── AppDelegate.m
│       │       │   │   │   ├── Base.lproj/
│       │       │   │   │   │   └── MainMenu.xib
│       │       │   │   │   ├── Info.plist
│       │       │   │   │   ├── lock-icon.icns
│       │       │   │   │   └── main.m
│       │       │   │   └── PrivilegedTaskExample.xcodeproj/
│       │       │   │       └── project.pbxproj
│       │       │   ├── README.md
│       │       │   ├── STPrivilegedTask.h
│       │       │   ├── STPrivilegedTask.m
│       │       │   └── STPrivilegedTask.podspec
│       │       └── Sparkle/
│       │           ├── .clang-format
│       │           ├── .gitignore
│       │           ├── .travis.yml
│       │           ├── CHANGELOG
│       │           ├── Configurations/
│       │           │   ├── ConfigBinaryDelta.xcconfig
│       │           │   ├── ConfigBinaryDeltaDebug.xcconfig
│       │           │   ├── ConfigBinaryDeltaRelease.xcconfig
│       │           │   ├── ConfigCommon.xcconfig
│       │           │   ├── ConfigCommonCoverage.xcconfig
│       │           │   ├── ConfigCommonDebug.xcconfig
│       │           │   ├── ConfigCommonRelease.xcconfig
│       │           │   ├── ConfigFileop.xcconfig
│       │           │   ├── ConfigFramework.xcconfig
│       │           │   ├── ConfigFrameworkDebug.xcconfig
│       │           │   ├── ConfigFrameworkRelease.xcconfig
│       │           │   ├── ConfigRelaunch.xcconfig
│       │           │   ├── ConfigRelaunchDebug.xcconfig
│       │           │   ├── ConfigRelaunchRelease.xcconfig
│       │           │   ├── ConfigTestApp.xcconfig
│       │           │   ├── ConfigTestAppDebug.xcconfig
│       │           │   ├── ConfigTestAppRelease.xcconfig
│       │           │   ├── ConfigUITest.xcconfig
│       │           │   ├── ConfigUITestCoverage.xcconfig
│       │           │   ├── ConfigUITestDebug.xcconfig
│       │           │   ├── ConfigUITestRelease.xcconfig
│       │           │   ├── ConfigUnitTest.xcconfig
│       │           │   ├── ConfigUnitTestCoverage.xcconfig
│       │           │   ├── ConfigUnitTestDebug.xcconfig
│       │           │   ├── ConfigUnitTestRelease.xcconfig
│       │           │   ├── make-release-package.sh
│       │           │   └── set-git-version-info.sh
│       │           ├── Documentation/
│       │           │   ├── .gitignore
│       │           │   ├── Doxyfile
│       │           │   └── build-docs.sh
│       │           ├── LICENSE
│       │           ├── Makefile
│       │           ├── README.markdown
│       │           ├── Resources/
│       │           │   ├── Images.xcassets/
│       │           │   │   └── AppIcon.appiconset/
│       │           │   │       └── Contents.json
│       │           │   ├── SUModelTranslation.plist
│       │           │   ├── SampleAppcast.xml
│       │           │   └── Sparkle-Icon.sketch
│       │           ├── Sparkle/
│       │           │   ├── Autoupdate/
│       │           │   │   ├── Autoupdate-Info.plist
│       │           │   │   └── Autoupdate.m
│       │           │   ├── CheckLocalizations.swift
│       │           │   ├── SUAppcast.h
│       │           │   ├── SUAppcast.m
│       │           │   ├── SUAppcastItem.h
│       │           │   ├── SUAppcastItem.m
│       │           │   ├── SUAutomaticUpdateAlert.h
│       │           │   ├── SUAutomaticUpdateAlert.m
│       │           │   ├── SUAutomaticUpdateDriver.h
│       │           │   ├── SUAutomaticUpdateDriver.m
│       │           │   ├── SUBasicUpdateDriver.h
│       │           │   ├── SUBasicUpdateDriver.m
│       │           │   ├── SUBinaryDeltaApply.h
│       │           │   ├── SUBinaryDeltaApply.m
│       │           │   ├── SUBinaryDeltaCommon.h
│       │           │   ├── SUBinaryDeltaCommon.m
│       │           │   ├── SUBinaryDeltaCreate.h
│       │           │   ├── SUBinaryDeltaCreate.m
│       │           │   ├── SUBinaryDeltaTool.m
│       │           │   ├── SUBinaryDeltaUnarchiver.h
│       │           │   ├── SUBinaryDeltaUnarchiver.m
│       │           │   ├── SUCodeSigningVerifier.h
│       │           │   ├── SUCodeSigningVerifier.m
│       │           │   ├── SUConstants.h
│       │           │   ├── SUConstants.m
│       │           │   ├── SUDSAVerifier.h
│       │           │   ├── SUDSAVerifier.m
│       │           │   ├── SUDiskImageUnarchiver.h
│       │           │   ├── SUDiskImageUnarchiver.m
│       │           │   ├── SUErrors.h
│       │           │   ├── SUExport.h
│       │           │   ├── SUFileManager.h
│       │           │   ├── SUFileManager.m
│       │           │   ├── SUGuidedPackageInstaller.h
│       │           │   ├── SUGuidedPackageInstaller.m
│       │           │   ├── SUHost.h
│       │           │   ├── SUHost.m
│       │           │   ├── SUInstaller.h
│       │           │   ├── SUInstaller.m
│       │           │   ├── SULog.h
│       │           │   ├── SULog.m
│       │           │   ├── SUOperatingSystem.h
│       │           │   ├── SUOperatingSystem.m
│       │           │   ├── SUPackageInstaller.h
│       │           │   ├── SUPackageInstaller.m
│       │           │   ├── SUPipedUnarchiver.h
│       │           │   ├── SUPipedUnarchiver.m
│       │           │   ├── SUPlainInstaller.h
│       │           │   ├── SUPlainInstaller.m
│       │           │   ├── SUProbingUpdateDriver.h
│       │           │   ├── SUProbingUpdateDriver.m
│       │           │   ├── SUScheduledUpdateDriver.h
│       │           │   ├── SUScheduledUpdateDriver.m
│       │           │   ├── SUStandardVersionComparator.h
│       │           │   ├── SUStandardVersionComparator.m
│       │           │   ├── SUStatus.xib
│       │           │   ├── SUStatusController.h
│       │           │   ├── SUStatusController.m
│       │           │   ├── SUSystemProfiler.h
│       │           │   ├── SUSystemProfiler.m
│       │           │   ├── SUUIBasedUpdateDriver.h
│       │           │   ├── SUUIBasedUpdateDriver.m
│       │           │   ├── SUUnarchiver.h
│       │           │   ├── SUUnarchiver.m
│       │           │   ├── SUUnarchiver_Private.h
│       │           │   ├── SUUpdateAlert.h
│       │           │   ├── SUUpdateAlert.m
│       │           │   ├── SUUpdateDriver.h
│       │           │   ├── SUUpdateDriver.m
│       │           │   ├── SUUpdatePermissionPrompt.h
│       │           │   ├── SUUpdatePermissionPrompt.m
│       │           │   ├── SUUpdater.h
│       │           │   ├── SUUpdater.m
│       │           │   ├── SUUpdater_Private.h
│       │           │   ├── SUUserInitiatedUpdateDriver.h
│       │           │   ├── SUUserInitiatedUpdateDriver.m
│       │           │   ├── SUVersionComparisonProtocol.h
│       │           │   ├── SUVersionDisplayProtocol.h
│       │           │   ├── SUWindowController.h
│       │           │   ├── SUWindowController.m
│       │           │   ├── Sparkle-Info.plist
│       │           │   ├── Sparkle.h
│       │           │   ├── Sparkle.pch
│       │           │   ├── ar.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ca.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── cs.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── da.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── de.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── el.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── en.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── es.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── fi.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── fr.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── he.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── is.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── it.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ja.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ko.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── nb.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── nl.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── no.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.strings
│       │           │   │   ├── SUUpdateAlert.strings
│       │           │   │   └── Sparkle.strings
│       │           │   ├── pl.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── pt_BR.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── pt_PT.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ro.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── ru.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── sk.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── sl.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── sv.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── th.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── tr.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── uk.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   ├── zh_CN.lproj/
│       │           │   │   ├── SUAutomaticUpdateAlert.xib
│       │           │   │   ├── SUUpdateAlert.xib
│       │           │   │   ├── SUUpdatePermissionPrompt.xib
│       │           │   │   └── Sparkle.strings
│       │           │   └── zh_TW.lproj/
│       │           │       ├── SUAutomaticUpdateAlert.xib
│       │           │       ├── SUUpdateAlert.xib
│       │           │       ├── SUUpdatePermissionPrompt.xib
│       │           │       └── Sparkle.strings
│       │           ├── Sparkle.podspec
│       │           ├── Sparkle.xcodeproj/
│       │           │   ├── project.pbxproj
│       │           │   ├── project.xcworkspace/
│       │           │   │   └── contents.xcworkspacedata
│       │           │   └── xcshareddata/
│       │           │       └── xcschemes/
│       │           │           ├── Distribution.xcscheme
│       │           │           ├── Sparkle.xcscheme
│       │           │           └── UITests.xcscheme
│       │           ├── TestApplication/
│       │           │   ├── English.lproj/
│       │           │   │   ├── InfoPlist.strings
│       │           │   │   └── MainMenu.xib
│       │           │   ├── SUTestApplicationDelegate.h
│       │           │   ├── SUTestApplicationDelegate.m
│       │           │   ├── SUTestWebServer.h
│       │           │   ├── SUTestWebServer.m
│       │           │   ├── SUUpdateSettingsWindowController.h
│       │           │   ├── SUUpdateSettingsWindowController.m
│       │           │   ├── SUUpdateSettingsWindowController.xib
│       │           │   ├── TestApplication-Info.plist
│       │           │   ├── ar.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ca.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── cs.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── cy.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── da.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── de.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── el.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── es.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── fi.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── fr-CA.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── fr.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── he.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── hu.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── id.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── is.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── it.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ja.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ko.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── main.m
│       │           │   ├── nb.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── nl.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pl.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pt-BR.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pt-PT.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── pt.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ro.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── ru.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── sk.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── sl.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── sparkletestcast.xml
│       │           │   ├── sv.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── test_app_only_dsa_priv_dont_ever_do_this_for_real.pem
│       │           │   ├── test_app_only_dsa_pub.pem
│       │           │   ├── th.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── tr.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── uk.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   ├── zh-Hans.lproj/
│       │           │   │   └── InfoPlist.strings
│       │           │   └── zh-Hant.lproj/
│       │           │       └── InfoPlist.strings
│       │           ├── Tests/
│       │           │   ├── Resources/
│       │           │   │   ├── SparkleTestCodeSignApp.dmg
│       │           │   │   ├── SparkleTestCodeSignApp.enc.dmg
│       │           │   │   ├── SparkleTestCodeSignApp.tar.bz2
│       │           │   │   ├── SparkleTestCodeSignApp.tar.xz
│       │           │   │   ├── signed-test-file.txt
│       │           │   │   ├── test-pubkey.pem
│       │           │   │   ├── test.sparkle_guided.pkg
│       │           │   │   ├── testappcast.xml
│       │           │   │   └── testnamespaces.xml
│       │           │   ├── SUAppcastTest.swift
│       │           │   ├── SUBinaryDeltaTest.m
│       │           │   ├── SUCodeSigningVerifierTest.m
│       │           │   ├── SUDSAVerifierTest.m
│       │           │   ├── SUFileManagerTest.swift
│       │           │   ├── SUInstallerTest.m
│       │           │   ├── SUUnarchiverTest.swift
│       │           │   ├── SUUpdaterTest.m
│       │           │   ├── SUVersionComparisonTest.m
│       │           │   ├── Sparkle Unit Tests-Bridging-Header.h
│       │           │   └── SparkleTests-Info.plist
│       │           ├── UITests/
│       │           │   ├── SUTestApplicationTest.swift
│       │           │   └── UITests-Info.plist
│       │           ├── Vendor/
│       │           │   ├── CocoatechCore/
│       │           │   │   ├── NTSynchronousTask.h
│       │           │   │   └── NTSynchronousTask.m
│       │           │   └── bsdiff/
│       │           │       ├── bscommon.c
│       │           │       ├── bscommon.h
│       │           │       ├── bsdiff.c
│       │           │       ├── bspatch.c
│       │           │       ├── bspatch.h
│       │           │       ├── sais.c
│       │           │       └── sais.h
│       │           ├── bin/
│       │           │   ├── generate_keys
│       │           │   └── sign_update
│       │           └── fileop/
│       │               ├── SUFileOperationConstants.h
│       │               ├── SUFileOperationConstants.m
│       │               └── fileop.m
│       ├── Docs/
│       │   ├── DistributingBitBar.md
│       │   └── URLScheme.md
│       ├── Makefile
│       ├── README.md
│       └── Scripts/
│           └── bitbar-bundler
├── pkg/
│   ├── metadata/
│   │   ├── categories_metadata.go
│   │   ├── categories_metadata_test.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── plugin_metadata.go
│   │   └── plugin_metadata_test.go
│   ├── plugins/
│   │   ├── README.md
│   │   ├── action.go
│   │   ├── action_test.go
│   │   ├── colors.go
│   │   ├── emoji.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── install.go
│   │   ├── install_test.go
│   │   ├── installed_plugins.go
│   │   ├── installed_plugins_test.go
│   │   ├── item_params.go
│   │   ├── item_params_test.go
│   │   ├── parse.go
│   │   ├── parse_test.go
│   │   ├── plugin.go
│   │   ├── plugin_darwin.go
│   │   ├── plugin_linux.go
│   │   ├── plugin_test.go
│   │   ├── plugin_windows.go
│   │   ├── refresh_interval.go
│   │   ├── refresh_interval_test.go
│   │   ├── testdata/
│   │   │   ├── broken-plugins/
│   │   │   │   ├── broken.1m.sh
│   │   │   │   └── wont-quit.1m.sh
│   │   │   ├── plugins/
│   │   │   │   ├── 001-multiple.1s.sh
│   │   │   │   ├── 001-multiple.1s.sh.vars.json
│   │   │   │   ├── 002-multiple.1s.sh
│   │   │   │   ├── dirs-should-be-ignored/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── expanded.1m.sh
│   │   │   │   ├── expanded.1m.sh.off
│   │   │   │   ├── expanded.1s.sh
│   │   │   │   ├── params.3s.sh
│   │   │   │   ├── simple.1m.sh
│   │   │   │   ├── simple.1m.sh.disabled
│   │   │   │   └── simple.1s.sh
│   │   │   ├── stub-api/
│   │   │   │   └── currency-tracker.1h.py.json
│   │   │   ├── token-too-long/
│   │   │   │   ├── jma.1h.sh
│   │   │   │   └── jma.1h.sh.output
│   │   │   └── vars-test/
│   │   │       ├── plugin.sh
│   │   │       └── plugin.sh.vars.json
│   │   ├── variables.go
│   │   └── variables_test.go
│   └── update/
│       ├── README.md
│       ├── go.mod
│       ├── go.sum
│       ├── update.go
│       ├── update_test.go
│       └── updatetest/
│           └── main.go
├── talk-overview.md
├── tools/
│   ├── sitegen/
│   │   ├── README.md
│   │   ├── docs.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── images.go
│   │   ├── main.go
│   │   ├── repo.go
│   │   ├── repo_test.go
│   │   └── run.sh
│   └── xbarmdcheck/
│       ├── README.md
│       ├── go.mod
│       ├── go.sum
│       ├── main.go
│       └── testdata/
│           └── sample-plugin.sh
└── xbarapp.com/
    ├── .gcloudignore
    ├── README.md
    ├── app.yaml
    ├── articles/
    │   └── 2021/
    │       └── 03/
    │           ├── 13/
    │           │   └── Upgrade-legacy-BitBar-plugins.md
    │           └── 14/
    │               └── Variables-in-xbar.md
    ├── build.sh
    ├── deploy.sh
    ├── download.go
    ├── gen.sh
    ├── go.mod
    ├── go.sum
    ├── main.go
    ├── main_test.go
    ├── package.json
    ├── postcss.config.js
    ├── public/
    │   └── css/
    │       └── xbar.css
    ├── run.sh
    ├── styles.css
    ├── tailwind.config.js
    └── templates/
        ├── _layout.html
        ├── article.html
        ├── articles-index.html
        ├── category.html
        ├── contributor.html
        ├── contributors.html
        ├── index.html
        └── plugin.html
Download .txt
SYMBOL INDEX (364 symbols across 64 files)

FILE: app/app.go
  type app (line 40) | type app struct
    method Start (line 167) | func (app *app) Start(runtime *wails.Runtime) {
    method RefreshAll (line 195) | func (app *app) RefreshAll() {
    method CheckForUpdates (line 260) | func (app *app) CheckForUpdates() {
    method onMenuWillOpen (line 264) | func (app *app) onMenuWillOpen() {
    method onMenuDidClose (line 270) | func (app *app) onMenuDidClose() {
    method updateStartOnLogin (line 276) | func (app *app) updateStartOnLogin(data *menu.CallbackData) {
    method updateAutoupdate (line 287) | func (app *app) updateAutoupdate(data *menu.CallbackData) {
    method onErr (line 311) | func (app *app) onErr(err string) {
    method Shutdown (line 329) | func (app *app) Shutdown() {
    method newXbarMenu (line 337) | func (app *app) newXbarMenu(plugin *plugins.Plugin, asSubmenu bool) *m...
    method createDefaultMenus (line 432) | func (app *app) createDefaultMenus() {
    method onPluginsMenuClicked (line 439) | func (app *app) onPluginsMenuClicked(_ *menu.CallbackData) {
    method onOpenPluginsFolderClicked (line 443) | func (app *app) onOpenPluginsFolderClicked(_ *menu.CallbackData) {
    method onQuitMenuClicked (line 447) | func (app *app) onQuitMenuClicked(_ *menu.CallbackData) {
    method onPluginsRefreshMenuClicked (line 451) | func (app *app) onPluginsRefreshMenuClicked(_ *menu.CallbackData, p *p...
    method onPluginsRefreshAllMenuClicked (line 455) | func (app *app) onPluginsRefreshAllMenuClicked(_ *menu.CallbackData) {
    method onBrowserRefreshMenuClicked (line 459) | func (app *app) onBrowserRefreshMenuClicked(_ *menu.CallbackData) {
    method onBrowserHardRefreshMenuClicked (line 463) | func (app *app) onBrowserHardRefreshMenuClicked(_ *menu.CallbackData) {
    method onCheckForUpdatesMenuClick (line 468) | func (app *app) onCheckForUpdatesMenuClick(_ *menu.CallbackData) {
    method onClearCacheMenuClicked (line 472) | func (app *app) onClearCacheMenuClicked(_ *menu.CallbackData) {
    method clearCache (line 476) | func (app *app) clearCache(passive bool) {
    method handleIncomingURL (line 515) | func (app *app) handleIncomingURL(url string) {
    method onRefresh (line 564) | func (app *app) onRefresh(ctx context.Context, p *plugins.Plugin, _ er...
    method onCycle (line 589) | func (app *app) onCycle(_ context.Context, p *plugins.Plugin) {
    method refreshMenus (line 610) | func (app *app) refreshMenus() {
    method updateLabel (line 618) | func (app *app) updateLabel(tray *menu.TrayMenu, p *plugins.Plugin) bo...
    method checkForUpdates (line 640) | func (app *app) checkForUpdates(passive bool) {
    method setDarkMode (line 781) | func (app *app) setDarkMode(darkmode bool) {
  function newApp (line 87) | func newApp() (*app, error) {
  function tickOS (line 774) | func tickOS() {

FILE: app/categories_service.go
  type Category (line 11) | type Category struct
  type PathItem (line 19) | type PathItem struct
  type CategoriesService (line 26) | type CategoriesService struct
    method GetCategories (line 40) | func (c *CategoriesService) GetCategories() ([]Category, error) {
  function NewCategoriesService (line 32) | func NewCategoriesService(client *http.Client) *CategoriesService {

FILE: app/categories_service_test.go
  function TestCategoryRepositoryGetCategories (line 11) | func TestCategoryRepositoryGetCategories(t *testing.T) {

FILE: app/command_service.go
  type CommandService (line 14) | type CommandService struct
    method ClearCache (line 28) | func (c *CommandService) ClearCache() {
    method RefreshAllPlugins (line 33) | func (c *CommandService) RefreshAllPlugins() {
    method WindowHide (line 38) | func (c *CommandService) WindowHide() {
    method WindowMinimise (line 43) | func (c *CommandService) WindowMinimise() {
    method OpenPath (line 48) | func (c CommandService) OpenPath(path string) error {
    method OpenURL (line 57) | func (c CommandService) OpenURL(url string) error {
    method OpenFile (line 66) | func (c CommandService) OpenFile(path string) error {
    method runCommand (line 76) | func (CommandService) runCommand(name string, args ...string) error {
  function NewCommandService (line 21) | func NewCommandService(OnRefresh func()) *CommandService {

FILE: app/frontend/rollup.config.js
  function handleRollupWarning (line 93) | function handleRollupWarning(warning) {
  function serve (line 97) | function serve() {

FILE: app/incoming_urls.go
  type incomingURL (line 10) | type incomingURL struct
  function parseIncomingURL (line 18) | func parseIncomingURL(urlStr string) (incomingURL, error) {

FILE: app/incoming_urls_test.go
  function TestParseIncomingURL (line 9) | func TestParseIncomingURL(t *testing.T) {

FILE: app/main.go
  function main (line 18) | func main() {
  function run (line 27) | func run() error {

FILE: app/menu_parser.go
  type MenuParser (line 15) | type MenuParser struct
    method ParseItems (line 23) | func (m MenuParser) ParseItems(ctx context.Context, items []*plugins.I...
    method ParseMenuItem (line 44) | func (m MenuParser) ParseMenuItem(ctx context.Context, item *plugins.I...
  function NewMenuParser (line 18) | func NewMenuParser() *MenuParser {

FILE: app/menu_parser_test.go
  function TestMenuParser (line 14) | func TestMenuParser(t *testing.T) {
  function JSON (line 143) | func JSON(menu *menu.Menu, is *is.I) string {

FILE: app/person_service.go
  type PersonService (line 13) | type PersonService struct
    method GetPersonDetails (line 33) | func (p *PersonService) GetPersonDetails(githubUsername string) (*Pers...
  function NewPersonService (line 19) | func NewPersonService(client *http.Client) *PersonService {
  type PersonDetails (line 27) | type PersonDetails struct

FILE: app/plugins_service.go
  type PluginsService (line 23) | type PluginsService struct
    method GetPlugins (line 49) | func (p *PluginsService) GetPlugins(categoryPath string) ([]metadata.P...
    method GetPlugin (line 77) | func (p *PluginsService) GetPlugin(pluginPath string) (*metadata.Plugi...
    method GetFeaturedPlugins (line 105) | func (p *PluginsService) GetFeaturedPlugins() ([]metadata.Plugin, erro...
    method GetInstalledPlugins (line 133) | func (p *PluginsService) GetInstalledPlugins() ([]plugins.InstalledPlu...
    method InstallPlugin (line 140) | func (p *PluginsService) InstallPlugin(plugin metadata.Plugin) (string...
    method UninstallPlugin (line 190) | func (p *PluginsService) UninstallPlugin(installedPluginInfo Uninstall...
    method GetInstalledPluginMetadata (line 234) | func (p *PluginsService) GetInstalledPluginMetadata(installedPluginPat...
    method LoadVariableValues (line 259) | func (p *PluginsService) LoadVariableValues(installedPluginPath string...
    method SaveVariableValues (line 267) | func (p *PluginsService) SaveVariableValues(installedPluginPath string...
    method SetEnabled (line 276) | func (p *PluginsService) SetEnabled(installedPluginPath string, enable...
    method SetRefreshInterval (line 295) | func (p *PluginsService) SetRefreshInterval(installedPluginPath string...
  function NewPluginsService (line 41) | func NewPluginsService(client *http.Client, baseURL string) *PluginsServ...
  type UninstallPluginRequest (line 184) | type UninstallPluginRequest struct
  type InstalledPluginMetadata (line 226) | type InstalledPluginMetadata struct
  type SetRefreshIntervalResult (line 289) | type SetRefreshIntervalResult struct

FILE: app/settings.go
  type settings (line 13) | type settings struct
    method setDefaults (line 27) | func (s *settings) setDefaults() {
    method save (line 72) | func (s *settings) save() error {
  function loadSettings (line 51) | func loadSettings(path string) (*settings, error) {

FILE: app/settings_test.go
  function TestSettings (line 11) | func TestSettings(t *testing.T) {

FILE: archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings/AHProxy.h
  type kAHProxyTypeUnknown (line 28) | typedef NS_ENUM(NSInteger, AHProxyType) {

FILE: archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriod.h
  type DTTimePeriodRelationAfter (line 26) | typedef NS_ENUM(NSUInteger, DTTimePeriodRelation){
  type DTTimePeriodSizeSecond (line 43) | typedef NS_ENUM(NSUInteger, DTTimePeriodSize) {
  type DTTimePeriodIntervalOpen (line 53) | typedef NS_ENUM(NSUInteger, DTTimePeriodInterval) {
  type DTTimePeriodAnchorStart (line 58) | typedef NS_ENUM(NSUInteger, DTTimePeriodAnchor) {

FILE: archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodChain.h
  function interface (line 26) | interface DTTimePeriodChain : DTTimePeriodGroup {

FILE: archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodGroup.h
  function interface (line 26) | interface DTTimePeriodGroup : NSObject {

FILE: archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/Colours.h
  type ColorSchemeAnalagous (line 61) | typedef NS_ENUM(NSInteger, ColorScheme) {
  type ColorFormulationRGBA (line 69) | typedef NS_ENUM(NSInteger, ColorFormulation) {
  type ColorDistanceCIE76 (line 77) | typedef NS_ENUM(NSInteger, ColorDistance) {

FILE: archive/bitbar/App/Vendor/STPrivilegedTask/STPrivilegedTask.h
  function interface (line 43) | interface STPrivilegedTask : NSObject

FILE: archive/bitbar/App/Vendor/Sparkle/Sparkle/SUAutomaticUpdateAlert.h
  type SUInstallNowChoice (line 14) | typedef NS_ENUM(NSInteger, SUAutomaticInstallationChoice) {

FILE: archive/bitbar/App/Vendor/Sparkle/Sparkle/SUHost.h
  type NSOperatingSystemVersion (line 15) | typedef struct {

FILE: archive/bitbar/App/Vendor/Sparkle/Sparkle/SUUpdateAlert.h
  type SUInstallUpdateChoice (line 17) | typedef NS_ENUM(NSInteger, SUUpdateAlertChoice) {

FILE: archive/bitbar/App/Vendor/Sparkle/Sparkle/SUUpdatePermissionPrompt.h
  type SUAutomaticallyCheck (line 14) | typedef NS_ENUM(NSInteger, SUPermissionPromptResult) {

FILE: archive/bitbar/App/Vendor/Sparkle/Vendor/bsdiff/bscommon.c
  function u_char (line 11) | u_char *readfile(const char *filename, off_t *outSize)

FILE: archive/bitbar/App/Vendor/Sparkle/Vendor/bsdiff/bsdiff.c
  function off_t (line 48) | static off_t matchlen(u_char *old, off_t oldsize, u_char *new, off_t new...
  function off_t (line 68) | static off_t search(off_t *I, u_char *old, off_t oldsize,
  function offtout (line 97) | static void offtout(off_t x, u_char *buf)
  function bsdiff (line 122) | int bsdiff(int argc, char *argv[])

FILE: archive/bitbar/App/Vendor/Sparkle/Vendor/bsdiff/bspatch.c
  type io_funcs_t (line 47) | typedef struct
  function stream_t (line 54) | static stream_t BSDIFF40_open(FILE *f)
  function BSDIFF40_close (line 64) | static void BSDIFF40_close(stream_t s)
  function off_t (line 70) | static off_t BSDIFF40_read(stream_t s, void *buf, off_t len)
  function stream_t (line 88) | static stream_t BSDIFN40_open(FILE *f)
  function BSDIFN40_close (line 93) | static void BSDIFN40_close(stream_t __unused s)
  function off_t (line 97) | static off_t BSDIFN40_read(stream_t s, void *buf, off_t len)
  type u_char (line 110) | typedef unsigned char u_char;
  function off_t (line 113) | static off_t offtin(u_char *buf)
  function bspatch (line 131) | int bspatch(int argc,const char * const argv[])

FILE: archive/bitbar/App/Vendor/Sparkle/Vendor/bsdiff/sais.c
  function getCounts (line 46) | static
  function getBuckets (line 53) | static
  function LMSsort1 (line 62) | static
  function sais_index_type (line 103) | static
  function LMSsort2 (line 149) | static
  function sais_index_type (line 208) | static
  function induceSA (line 249) | static
  function sais_index_type (line 287) | static
  function sais_index_type (line 333) | static
  function sais_index_type (line 492) | sais_index_type
  function sais_index_type (line 499) | sais_index_type
  function sais_index_type (line 506) | sais_index_type
  function sais_index_type (line 521) | sais_index_type

FILE: pkg/metadata/categories_metadata.go
  type Category (line 9) | type Category struct
    method LastUpdatedFormatted (line 21) | func (c Category) LastUpdatedFormatted() string {
  function CategoryWalk (line 27) | func CategoryWalk(categories map[string]Category, fn func(Category)) {
  function CategoryEnsurePath (line 36) | func CategoryEnsurePath(categories map[string]Category, parentPath []str...

FILE: pkg/metadata/categories_metadata_test.go
  function TestCategoryMapper (line 9) | func TestCategoryMapper(t *testing.T) {

FILE: pkg/metadata/plugin_metadata.go
  type Plugin (line 18) | type Plugin struct
    method Validate (line 69) | func (p Plugin) Validate() error {
    method NiceDesc (line 86) | func (p Plugin) NiceDesc() string {
    method LastUpdatedFormatted (line 142) | func (p Plugin) LastUpdatedFormatted() string {
    method Complete (line 187) | func (p Plugin) Complete() error {
  type File (line 94) | type File struct
  type PluginVar (line 104) | type PluginVar struct
    method DefaultValue (line 120) | func (p PluginVar) DefaultValue() interface{} {
  type Person (line 147) | type Person struct
    method Desc (line 156) | func (p Person) Desc() string {
    method NiceBio (line 170) | func (p Person) NiceBio() string {
  function Parse (line 195) | func Parse(debugf DebugFunc, filename, s string) (Plugin, error) {
  type PathItem (line 279) | type PathItem struct
  function CategoryPathSegments (line 287) | func CategoryPathSegments(categoryPath string) []PathItem {
  function splitList (line 303) | func splitList(s string) []string {
  type DebugFunc (line 317) | type DebugFunc
  function DebugfNoop (line 320) | func DebugfNoop(format string, v ...interface{}) {}
  function DebugfLog (line 323) | func DebugfLog(format string, v ...interface{}) {
  constant DefaultPluginImage (line 328) | DefaultPluginImage = "https://xbarapp.com/public/img/xbar-2048.png"
  function HasImage (line 332) | func HasImage(imageURL string) bool {
  function RandomPlugins (line 343) | func RandomPlugins(pluginsByPath map[string][]Plugin, pathPrefix string,...
  function parsePluginVar (line 369) | func parsePluginVar(s string) (PluginVar, error) {
  type errMissingMetadata (line 449) | type errMissingMetadata
    method Error (line 451) | func (e errMissingMetadata) Error() string {
  type errParse (line 455) | type errParse struct
    method Error (line 460) | func (e errParse) Error() string {
  function truncate (line 468) | func truncate(s string, max int) string {

FILE: pkg/metadata/plugin_metadata_test.go
  function TestParse (line 10) | func TestParse(t *testing.T) {
  function TestPluginCategoryPathSegments (line 99) | func TestPluginCategoryPathSegments(t *testing.T) {
  function TestVariables (line 119) | func TestVariables(t *testing.T) {
  function TestErrors (line 178) | func TestErrors(t *testing.T) {

FILE: pkg/plugins/action.go
  type ActionFunc (line 18) | type ActionFunc
  constant actionTimeout (line 22) | actionTimeout = 10 * time.Second
  method Action (line 33) | func (i *Item) Action() ActionFunc {
  function actionFuncs (line 68) | func actionFuncs(actions ...ActionFunc) ActionFunc {
  function actionHref (line 81) | func actionHref(debugf DebugFunc, href string) ActionFunc {
  function actionShell (line 111) | func actionShell(debugf DebugFunc, item *Item, appleScriptTemplate, comm...
  function actionShellTerminal (line 141) | func actionShellTerminal(debugf DebugFunc, item *Item, appleScriptTempla...
  function actionRefresh (line 161) | func actionRefresh(debugf DebugFunc, refreshFunc func(ctx context.Contex...

FILE: pkg/plugins/action_test.go
  function OffTestItemAction (line 10) | func OffTestItemAction(t *testing.T) {
  function TestTerminal (line 56) | func TestTerminal(t *testing.T) {

FILE: pkg/plugins/emoji.go
  function Emojize (line 907) | func Emojize(s string) string {

FILE: pkg/plugins/install.go
  type Installer (line 23) | type Installer struct
    method Uninstall (line 29) | func (i Installer) Uninstall(installedPluginPath string) error {
    method Install (line 39) | func (i Installer) Install(pluginPath *url.URL) (string, error) {
    method fetchPlugin (line 63) | func (i Installer) fetchPlugin(pluginPath *url.URL) (metadata.Plugin, ...
    method getInstalledPluginName (line 85) | func (i Installer) getInstalledPluginName(plugin metadata.Plugin) (str...
    method writePluginFiles (line 106) | func (i Installer) writePluginFiles(dstPath string, plugin metadata.Pl...

FILE: pkg/plugins/install_test.go
  function TestInstall (line 15) | func TestInstall(t *testing.T) {
  function TestGetInstalledPluginName (line 131) | func TestGetInstalledPluginName(t *testing.T) {

FILE: pkg/plugins/installed_plugins.go
  type InstalledPlugin (line 15) | type InstalledPlugin struct
  constant disabledPluginExtension (line 25) | disabledPluginExtension = ".off"
  function IsPluginEnabled (line 28) | func IsPluginEnabled(pluginPath string) bool {
  function SetEnabled (line 34) | func SetEnabled(pluginDirectory, installedPluginPath string, enabled boo...
  function GetInstalledPlugins (line 54) | func GetInstalledPlugins(pluginDirectory string) ([]InstalledPlugin, err...

FILE: pkg/plugins/installed_plugins_test.go
  function TestGetInstalledPlugins (line 11) | func TestGetInstalledPlugins(t *testing.T) {
  function TestIsPluginEnabled (line 42) | func TestIsPluginEnabled(t *testing.T) {
  function TestSetEnabled (line 50) | func TestSetEnabled(t *testing.T) {

FILE: pkg/plugins/item_params.go
  type Items (line 12) | type Items struct
  type Item (line 21) | type Item struct
    method DisplayText (line 42) | func (i Item) DisplayText() string {
  type ItemParams (line 55) | type ItemParams struct
    method setValueByKey (line 177) | func (p *ItemParams) setValueByKey(key, value string) error {
  function parseParams (line 109) | func parseParams(s string) (string, ItemParams, error) {
  function parseParamStr (line 124) | func parseParamStr(params *ItemParams, s string) error {
  function parseBool (line 277) | func parseBool(s string) (bool, error) {
  function parseColor (line 288) | func parseColor(s string) (string, error) {
  function parseInt (line 309) | func parseInt(s string) (int, error) {
  function truncate (line 318) | func truncate(s string, max int) string {

FILE: pkg/plugins/item_params_test.go
  function TestParseParamStr (line 11) | func TestParseParamStr(t *testing.T) {
  function TestLength (line 103) | func TestLength(t *testing.T) {
  function TestDropdown (line 126) | func TestDropdown(t *testing.T) {
  function TestAlternate (line 149) | func TestAlternate(t *testing.T) {
  function TestTrim (line 176) | func TestTrim(t *testing.T) {
  function TestSeparator (line 194) | func TestSeparator(t *testing.T) {
  function TestBlankLines (line 217) | func TestBlankLines(t *testing.T) {
  function TestErrors (line 236) | func TestErrors(t *testing.T) {
  function TestTruncate (line 261) | func TestTruncate(t *testing.T) {
  function TestGoodColors (line 272) | func TestGoodColors(t *testing.T) {
  function TestBadColors (line 295) | func TestBadColors(t *testing.T) {
  function TestQuotedParams (line 321) | func TestQuotedParams(t *testing.T) {

FILE: pkg/plugins/parse.go
  constant nesting (line 13) | nesting   = "--"
  constant separator (line 14) | separator = "---"
  method parseOutput (line 19) | func (p *Plugin) parseOutput(ctx context.Context, filename string, r io....
  function parseSeparator (line 131) | func parseSeparator(src string) (string, bool) {

FILE: pkg/plugins/parse_test.go
  function TestPluginParams (line 14) | func TestPluginParams(t *testing.T) {
  function TestParseParams (line 31) | func TestParseParams(t *testing.T) {
  function TestParseErrors (line 68) | func TestParseErrors(t *testing.T) {
  function TestParseEmoji (line 89) | func TestParseEmoji(t *testing.T) {
  function TestParseMultiplePipes (line 108) | func TestParseMultiplePipes(t *testing.T) {
  function TestStartWithPipe (line 125) | func TestStartWithPipe(t *testing.T) {
  function TestTokenTooLong (line 137) | func TestTokenTooLong(t *testing.T) {
  function TestNestedSeparators (line 156) | func TestNestedSeparators(t *testing.T) {
  function TestParseSeparator (line 200) | func TestParseSeparator(t *testing.T) {

FILE: pkg/plugins/plugin.go
  type RefreshFunc (line 23) | type RefreshFunc
  type CycleFunc (line 26) | type CycleFunc
  type DebugFunc (line 28) | type DebugFunc
  type Plugin (line 32) | type Plugin struct
    method CleanFilename (line 79) | func (p Plugin) CleanFilename() string {
    method cycle (line 88) | func (p *Plugin) cycle(ctx context.Context) {
    method Run (line 185) | func (p *Plugin) Run(ctx context.Context) {
    method TriggerRefresh (line 241) | func (p *Plugin) TriggerRefresh() {
    method Refresh (line 262) | func (p *Plugin) Refresh(ctx context.Context) {
    method CurrentCycleItem (line 275) | func (p *Plugin) CurrentCycleItem() *Item {
    method RunInTerminal (line 287) | func (p *Plugin) RunInTerminal(appleScriptTemplate3 string) error {
    method refresh (line 293) | func (p *Plugin) refresh(ctx context.Context) error {
    method OnErr (line 333) | func (p *Plugin) OnErr(err error) {
    method stringToItems (line 360) | func (p *Plugin) stringToItems(s string) []*Item {
  type Plugins (line 100) | type Plugins
    method Run (line 106) | func (p Plugins) Run(ctx context.Context) {
    method Exist (line 119) | func (p Plugins) Exist(path string) bool {
  function Dir (line 130) | func Dir(path string) (Plugins, error) {
  function NewPlugin (line 162) | func NewPlugin(command string) *Plugin {
  type errExec (line 344) | type errExec struct
    method Error (line 351) | func (e errExec) Error() string {
  function DebugfNoop (line 409) | func DebugfNoop(format string, v ...interface{}) {}
  function DebugfLog (line 412) | func DebugfLog(format string, v ...interface{}) {
  function DebugfPrefix (line 417) | func DebugfPrefix(prefix string, debugf DebugFunc) DebugFunc {
  type errParsing (line 432) | type errParsing struct
    method Error (line 439) | func (e *errParsing) Error() string {
  function variablesEnvString (line 443) | func variablesEnvString(vars []string) string {

FILE: pkg/plugins/plugin_darwin.go
  function Setpgid (line 14) | func Setpgid(cmd *exec.Cmd) {
  method runInTerminal (line 20) | func (p *Plugin) runInTerminal(appleScriptTemplate3, command, paramsStr ...

FILE: pkg/plugins/plugin_linux.go
  function Setpgid (line 12) | func Setpgid(cmd *exec.Cmd) {
  method runInTerminal (line 18) | func (p *Plugin) runInTerminal(appleScriptTemplate3, command, paramsStr ...

FILE: pkg/plugins/plugin_test.go
  function TestRun (line 15) | func TestRun(t *testing.T) {
  function TestRunStderr (line 59) | func TestRunStderr(t *testing.T) {
  function TestPluginsDir (line 73) | func TestPluginsDir(t *testing.T) {
  function TestPluginsExist (line 81) | func TestPluginsExist(t *testing.T) {
  function TestPluginOnRefresh (line 91) | func TestPluginOnRefresh(t *testing.T) {
  function TestPluginSimple (line 111) | func TestPluginSimple(t *testing.T) {
  function TestPluginExpanded (line 126) | func TestPluginExpanded(t *testing.T) {
  function TestCycleItems (line 145) | func TestCycleItems(t *testing.T) {
  function TestSubmenus (line 206) | func TestSubmenus(t *testing.T) {
  function TestOnErr (line 249) | func TestOnErr(t *testing.T) {
  function TestCleanFilename (line 266) | func TestCleanFilename(t *testing.T) {
  function TestPluginWontQuit (line 288) | func TestPluginWontQuit(t *testing.T) {
  function TestVariablesEnvString (line 303) | func TestVariablesEnvString(t *testing.T) {
  function TestPipeElsewhere (line 321) | func TestPipeElsewhere(t *testing.T) {

FILE: pkg/plugins/plugin_windows.go
  function Setpgid (line 12) | func Setpgid(cmd *exec.Cmd) {
  method runInTerminal (line 15) | func (p *Plugin) runInTerminal(appleScriptTemplate3, command, paramsStr ...

FILE: pkg/plugins/refresh_interval.go
  type RefreshInterval (line 23) | type RefreshInterval struct
    method Duration (line 29) | func (r RefreshInterval) Duration() time.Duration {
    method String (line 49) | func (r RefreshInterval) String() string {
  function SetRefreshInterval (line 67) | func SetRefreshInterval(pluginDirectory, installedPluginPath string, ref...
  function validateRefreshInterval (line 99) | func validateRefreshInterval(refreshInterval RefreshInterval) error {
  function ParseFilenameInterval (line 113) | func ParseFilenameInterval(filename string) (RefreshInterval, error) {
  function findIntervalInFilename (line 127) | func findIntervalInFilename(filename string) string {
  function parseInterval (line 147) | func parseInterval(interval string) (RefreshInterval, error) {

FILE: pkg/plugins/refresh_interval_test.go
  function TestSetRefreshInterval (line 13) | func TestSetRefreshInterval(t *testing.T) {
  function TestParseFilenameInterval (line 125) | func TestParseFilenameInterval(t *testing.T) {
  function TestValidateRefreshInterval (line 157) | func TestValidateRefreshInterval(t *testing.T) {
  function TestRefreshIntervalString (line 184) | func TestRefreshIntervalString(t *testing.T) {

FILE: pkg/plugins/variables.go
  constant variableJSONFileExt (line 17) | variableJSONFileExt = ".vars.json"
  function SaveVariableValues (line 20) | func SaveVariableValues(pluginDir, installedPluginPath string, values ma...
  function LoadVariableValues (line 34) | func LoadVariableValues(pluginDir, installedPluginPath string) (map[stri...
  method loadVariablesAsEnvVars (line 57) | func (p *Plugin) loadVariablesAsEnvVars() ([]string, error) {
  method loadVariables (line 69) | func (p *Plugin) loadVariables() (map[string]interface{}, error) {
  method loadVariablesFromJSONFile (line 100) | func (p *Plugin) loadVariablesFromJSONFile() (map[string]interface{}, er...
  method loadVariablesFromPluginMetadata (line 121) | func (p *Plugin) loadVariablesFromPluginMetadata() (map[string]interface...

FILE: pkg/plugins/variables_test.go
  function TestEnvironmentVariables (line 14) | func TestEnvironmentVariables(t *testing.T) {
  function TestVariablesPersistence (line 44) | func TestVariablesPersistence(t *testing.T) {

FILE: pkg/update/update.go
  type Updater (line 22) | type Updater struct
    method Update (line 45) | func (u *Updater) Update() (*Release, error) {
    method Restart (line 82) | func (u *Updater) Restart() error {
    method getLatestRelease (line 114) | func (u *Updater) getLatestRelease() (*Release, error) {
    method HasUpdate (line 140) | func (u *Updater) HasUpdate() (*Release, bool, error) {
    method downloadAndReplaceApp (line 180) | func (u *Updater) downloadAndReplaceApp(asset Asset) error {
  type SelectAssetFunc (line 41) | type SelectAssetFunc
  function hasUpdate (line 154) | func hasUpdate(current, latest string) bool {
  type Release (line 234) | type Release struct
  type Asset (line 243) | type Asset struct
  function appPathFromExecutable (line 250) | func appPathFromExecutable(p string) (string, error) {

FILE: pkg/update/update_test.go
  function TestForReals (line 17) | func TestForReals(t *testing.T) {
  function TestUpdate (line 46) | func TestUpdate(t *testing.T) {
  function TestAppPathFromExecutable (line 106) | func TestAppPathFromExecutable(t *testing.T) {
  function TestMatchingVersions (line 114) | func TestMatchingVersions(t *testing.T) {
  function TestLocalVersionIsHigher (line 162) | func TestLocalVersionIsHigher(t *testing.T) {

FILE: pkg/update/updatetest/main.go
  function main (line 12) | func main() {
  function run (line 20) | func run() error {

FILE: tools/sitegen/docs.go
  function generateDocs (line 36) | func generateDocs(ctx context.Context) ([]Article, error) {
  type Article (line 97) | type Article struct
  type docsGenerator (line 109) | type docsGenerator struct
    method parseArticleSource (line 155) | func (g *docsGenerator) parseArticleSource(_ context.Context, path, de...
    method generateArticlePages (line 206) | func (g *docsGenerator) generateArticlePages() error {
    method generateArticlesIndexPage (line 238) | func (g *docsGenerator) generateArticlesIndexPage() error {
    method randomArticles (line 267) | func (g *docsGenerator) randomArticles(excluding string, n int) []Arti...
  function newDocsGenerator (line 116) | func newDocsGenerator() (*docsGenerator, error) {
  function copyFile (line 293) | func copyFile(dst, src string) (int64, error) {

FILE: tools/sitegen/images.go
  type imageDownloader (line 17) | type imageDownloader struct
    method DownloadImages (line 22) | func (d *imageDownloader) DownloadImages(plugins []metadata.Plugin) {
    method downloadImage (line 44) | func (d *imageDownloader) downloadImage(plugin *metadata.Plugin) error {

FILE: tools/sitegen/main.go
  function main (line 29) | func main() {
  function run (line 36) | func run(ctx context.Context, args []string) error {
  type generator (line 243) | type generator struct
    method mkdirall (line 308) | func (g *generator) mkdirall() error {
    method generateContributorPages (line 321) | func (g *generator) generateContributorPages(categories map[string]met...
    method generateAuthorJSON (line 347) | func (g *generator) generateAuthorJSON(pluginsByPath map[string][]meta...
    method generateContributorPage (line 385) | func (g *generator) generateContributorPage(categories map[string]meta...
    method generateContributorsPage (line 433) | func (g *generator) generateContributorsPage(categories map[string]met...
    method generatePluginsIndexPage (line 476) | func (g *generator) generatePluginsIndexPage(
    method generateCategoriesJSON (line 523) | func (g *generator) generateCategoriesJSON(categories map[string]metad...
    method generateCategoryPluginsJSONFiles (line 549) | func (g *generator) generateCategoryPluginsJSONFiles(categories map[st...
    method generateFeaturedPluginsJSON (line 572) | func (g *generator) generateFeaturedPluginsJSON(featuredPlugins []meta...
    method generateCategoryPluginsJSON (line 598) | func (g *generator) generateCategoryPluginsJSON(categoryPath string, p...
    method generateBigPluginsPayload (line 632) | func (g *generator) generateBigPluginsPayload(plugins []metadata.Plugi...
    method generatePluginPages (line 657) | func (g *generator) generatePluginPages(categories map[string]metadata...
    method generatePluginJSONPayload (line 669) | func (g generator) generatePluginJSONPayload(plugin metadata.Plugin) e...
    method generatePluginPage (line 699) | func (g *generator) generatePluginPage(categories map[string]metadata....
    method generateCategoryPages (line 736) | func (g *generator) generateCategoryPages(allcategories, categories ma...
    method generateCategoryPage (line 748) | func (g *generator) generateCategoryPage(categories map[string]metadat...
    method generateSitemap (line 796) | func (g *generator) generateSitemap(categories map[string]metadata.Cat...
    method categoriesToCategory (line 854) | func (g *generator) categoriesToCategory(categories map[string]metadat...
  function newGenerator (line 258) | func newGenerator(outputDir string) (*generator, error) {
  function firstSegment (line 872) | func firstSegment(path string) string {
  type cycleString (line 893) | type cycleString
    method Print (line 896) | func (c cycleString) Print(w io.Writer, i int) (int, error) {

FILE: tools/sitegen/repo.go
  type EachFunc (line 21) | type EachFunc
  type RepoReader (line 27) | type RepoReader struct
    method All (line 44) | func (r *RepoReader) All(ctx context.Context) error {
    method loadPluginMetadata (line 143) | func (r *RepoReader) loadPluginMetadata(ctx context.Context, gh *githu...

FILE: tools/sitegen/repo_test.go
  function TestReadRepo (line 13) | func TestReadRepo(t *testing.T) {

FILE: tools/xbarmdcheck/main.go
  function main (line 12) | func main() {
  function run (line 19) | func run() error {

FILE: xbarapp.com/download.go
  function downloadHandler (line 7) | func downloadHandler() http.HandlerFunc {

FILE: xbarapp.com/main.go
  function main (line 10) | func main() {
  function run (line 17) | func run() error {
  function serveFileHandler (line 32) | func serveFileHandler(filename string) http.HandlerFunc {

FILE: xbarapp.com/main_test.go
  function TestPluginMetadata (line 17) | func TestPluginMetadata(t *testing.T) {
  type pluginPayload (line 64) | type pluginPayload struct
  function loadPluginMetadata (line 70) | func loadPluginMetadata(is *is.I, path string) pluginPayload {
Condensed preview — 708 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,080K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 16,
    "preview": "github: matryer\n"
  },
  {
    "path": ".github/workflows/deploy-xbarappcom.yaml",
    "chars": 1200,
    "preview": "name: Build and Deploy\n\non:\n  push:\n    branches:\n      - main\n  schedule:\n    - cron: \"0 0 * * 0\" # every week\n\njobs:\n "
  },
  {
    "path": ".gitignore",
    "chars": 489,
    "preview": ".DS_Store\n.idea\napp/frontend/package.json.md5\nxbarapp.com/public/docs/\ntools/sitegen/sitegen\napp/node_modules\napp/build/"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 56,
    "preview": "{\n    \"files.exclude\": {\n        \"archive\": true\n    }\n}"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1095,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2023 Mat Ryer + contributors\n\nPermission is hereby granted, free of charge, to"
  },
  {
    "path": "README.md",
    "chars": 2801,
    "preview": "[![](xbarapp.com/public/img/xbar-menu-preview.png)](https://xbarapp.com/)\n\n# Welcome to xbar\n\nxbar (the BitBar reboot) l"
  },
  {
    "path": "app/.gitignore",
    "chars": 45,
    "preview": "app/frontend/package.json.md5\napp/build\n.idea"
  },
  {
    "path": "app/README.md",
    "chars": 1207,
    "preview": "# xbar\n\nPut anything into your macOS menu bar (sequel to BitBar)\n \n## Development\n\nTo build xbar, you will need:\n\n  * Go"
  },
  {
    "path": "app/app.go",
    "chars": 22148,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pkg/err"
  },
  {
    "path": "app/build.sh",
    "chars": 144,
    "preview": "#!/bin/bash\nset -e\n\nVERSION=`git describe --tags`\n\necho \"\"\necho \"  xbar ${VERSION}...\"\necho \"\"\necho -n $VERSION > .versi"
  },
  {
    "path": "app/categories_service.go",
    "chars": 1546,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\n// Category represents a group of plugins"
  },
  {
    "path": "app/categories_service_test.go",
    "chars": 399,
    "preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/matryer/is\"\n)\n\nfunc TestCategoryRepositoryGetCategor"
  },
  {
    "path": "app/command_service.go",
    "chars": 1842,
    "preview": "package main\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"syscall\"\n\n\t\"github.com/pkg/errors\"\n\twails \"github.com/wailsap"
  },
  {
    "path": "app/frontend/.gitignore",
    "chars": 100,
    "preview": "/node_modules/\n/public/build/\n\n.DS_Store\n/public/bundle.js.map\n/public/bundle.js\n/public/bundle.css\n"
  },
  {
    "path": "app/frontend/package.json",
    "chars": 1009,
    "preview": "{\n  \"name\": \"xbar\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"build\": \"rollup -c\",\n    \"dev\": \"rollup -c -w\",\n    \"start"
  },
  {
    "path": "app/frontend/postcss.config.js",
    "chars": 142,
    "preview": "module.exports = {\n    plugins: [\n        require('postcss-import'),\n        require('tailwindcss'),\n        require('au"
  },
  {
    "path": "app/frontend/public/index.html",
    "chars": 748,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\" />\n\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" "
  },
  {
    "path": "app/frontend/rollup.config.js",
    "chars": 2512,
    "preview": "import svelte from 'rollup-plugin-svelte'\nimport resolve from '@rollup/plugin-node-resolve'\nimport commonjs from '@rollu"
  },
  {
    "path": "app/frontend/src/App.svelte",
    "chars": 5374,
    "preview": "<script>\n\n\timport { Router } from 'svelte-hash-router'\n\timport { Events } from '@wails/runtime'\n\timport { \n\t\t\tcategories"
  },
  {
    "path": "app/frontend/src/Homepage.svelte",
    "chars": 1026,
    "preview": "<script>\n\n\timport { onMount } from 'svelte'\n\timport { getFeaturedPlugins, openURL } from './rpc.svelte'\n\timport A from '"
  },
  {
    "path": "app/frontend/src/InstalledPluginView.svelte",
    "chars": 6806,
    "preview": "<script>\n\timport { params } from 'svelte-hash-router';\n\timport {\n\t\tuninstallPlugin,\n\t\trefreshInstalledPlugins,\n\t\tgetInst"
  },
  {
    "path": "app/frontend/src/PersonView.svelte",
    "chars": 1793,
    "preview": "<script>\n\n\timport { params } from 'svelte-hash-router'\n\timport { getPersonDetails, openURL } from './rpc.svelte'\n\timport"
  },
  {
    "path": "app/frontend/src/PluginView.svelte",
    "chars": 2168,
    "preview": "<script>\n\n\timport { params } from 'svelte-hash-router'\n\timport { installedPlugins } from './pagedata.svelte'\n\timport { g"
  },
  {
    "path": "app/frontend/src/PluginsList.svelte",
    "chars": 1035,
    "preview": "<script>\n\n\timport { params } from 'svelte-hash-router'\n\timport Error from './elements/Error.svelte'\n\timport Breadcrumbs "
  },
  {
    "path": "app/frontend/src/backend/index.js",
    "chars": 5547,
    "preview": "// @ts-check\n// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT "
  },
  {
    "path": "app/frontend/src/backend/package.json",
    "chars": 238,
    "preview": "{\n  \"name\": \"backend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Package to wrap backend method calls\",\n  \"main\": \"index.j"
  },
  {
    "path": "app/frontend/src/elements/A.svelte",
    "chars": 219,
    "preview": "<script>\n    export let href = ''\n    export let underline = false\n    export let cssclass = ''\n</script>\n<a \n    on:cli"
  },
  {
    "path": "app/frontend/src/elements/Breadcrumbs.svelte",
    "chars": 1633,
    "preview": "<script>\n    \n    import A from './A.svelte'\n\n    // categoryPathSegments are the segments to show in the\n    // breadcr"
  },
  {
    "path": "app/frontend/src/elements/Button.svelte",
    "chars": 1003,
    "preview": "<script>\n\n\timport Spinner from './Spinner.svelte'\n\n\t// style is the style of button.\n\t// default or primary, see classes"
  },
  {
    "path": "app/frontend/src/elements/Duration.svelte",
    "chars": 1346,
    "preview": "<script>\n\timport { createEventDispatcher } from 'svelte'\n\tconst dispatch = createEventDispatcher()\n    import Spinner fr"
  },
  {
    "path": "app/frontend/src/elements/Error.svelte",
    "chars": 1678,
    "preview": "<script>\n\timport { slide } from 'svelte/transition'\n\timport { openURL } from '../rpc.svelte'\n\timport { fireSigRefresh } "
  },
  {
    "path": "app/frontend/src/elements/KeyboardShortcuts.svelte",
    "chars": 663,
    "preview": "<script>\n    \n    import { fireSigRefresh } from '../signals.svelte'\n\n    function handleKeydown(e) {\n        if (e.key "
  },
  {
    "path": "app/frontend/src/elements/PluginCollection.svelte",
    "chars": 2651,
    "preview": "<script>\n\timport Button from './Button.svelte'\n\timport A from './A.svelte'\n\timport { installedPlugins, selectInstalledPl"
  },
  {
    "path": "app/frontend/src/elements/PluginDetails.svelte",
    "chars": 1539,
    "preview": "<script>\n\n\t/*\n\t\n\t\tUsage:\n\n\t\t\t<PluginDetails plugin={plugin}>\n\t\t\t\t<div slot='action'>\n\t\t\t\t\tActions like switches etc\n\t\t\t\t"
  },
  {
    "path": "app/frontend/src/elements/PluginSourceBrowser.svelte",
    "chars": 885,
    "preview": "<script>\n\n    import { createEventDispatcher } from 'svelte'\n    import Button from './Button.svelte'\n\n    const dispatc"
  },
  {
    "path": "app/frontend/src/elements/Spinner.svelte",
    "chars": 1870,
    "preview": "<script>\n    export let waiter = 0\n    export let visible = false\n    \n    $: waiterUpdated(waiter)\n\n    export let widt"
  },
  {
    "path": "app/frontend/src/elements/Switch.svelte",
    "chars": 1810,
    "preview": "<script>\n\n\timport { onMount, createEventDispatcher } from 'svelte'\n\tconst dispatch = createEventDispatcher()\n\n\texport le"
  },
  {
    "path": "app/frontend/src/elements/VariableInput.svelte",
    "chars": 2449,
    "preview": "<script>\n\n\timport { createEventDispatcher } from 'svelte'\n\n    // dispatch handles the following events:\n    // \t\ton:cha"
  },
  {
    "path": "app/frontend/src/elements/Variables.svelte",
    "chars": 1935,
    "preview": "<script>\n\timport { createEventDispatcher } from 'svelte'\n    import { refreshAllPlugins } from '../rpc.svelte'\n\n    // d"
  },
  {
    "path": "app/frontend/src/main.js",
    "chars": 627,
    "preview": "import App from './App.svelte'\nimport { ready } from '@wails/runtime'\nimport { routes } from 'svelte-hash-router'\nimport"
  },
  {
    "path": "app/frontend/src/pagedata.svelte",
    "chars": 864,
    "preview": "<script context='module'>\n\n    import { writable } from 'svelte/store'\n\n    export const categories = writable(null)\n   "
  },
  {
    "path": "app/frontend/src/rpc.svelte",
    "chars": 2808,
    "preview": "<script context='module'>\n\n\texport function openURL(url) {\n\t\treturn backend.main.CommandService.OpenURL(url)\n\t}\n\n\texport"
  },
  {
    "path": "app/frontend/src/signals.svelte",
    "chars": 215,
    "preview": "<script context='module'>\n\n    import { writable } from \"svelte/store\"\n\n    export const sigRefresh = writable(new Date("
  },
  {
    "path": "app/frontend/src/styles.css",
    "chars": 2215,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n* {\n\tcursor: default !important;\n}\n\nhtml {\n\tfont-family: -ap"
  },
  {
    "path": "app/frontend/src/waiters.svelte",
    "chars": 907,
    "preview": "<script context='module'>\n\n    /*\n    \n        The globalWaiter drives the app spinner.\n\n        Whenever you're going t"
  },
  {
    "path": "app/frontend/tailwind.config.js",
    "chars": 343,
    "preview": "\nconst colors = require('tailwindcss/colors')\n\nmodule.exports = {\n\tpurge: [\n\t\t'./src/**/*.html',\n\t\t'./src/**/*.js',\n\t\t'."
  },
  {
    "path": "app/go.mod",
    "chars": 1194,
    "preview": "module github.com/matryer/xbar/app\n\ngo 1.16\n\nrequire (\n\tgithub.com/go-ole/go-ole v1.2.5 // indirect\n\tgithub.com/google/b"
  },
  {
    "path": "app/go.sum",
    "chars": 22649,
    "preview": "github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=\ngithub.com/Masterminds/semver v1.5."
  },
  {
    "path": "app/incoming_urls.go",
    "chars": 803,
    "preview": "package main\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\ntype incomingURL struct {\n\t// Action is the ac"
  },
  {
    "path": "app/incoming_urls_test.go",
    "chars": 808,
    "preview": "package main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/matryer/is\"\n)\n\nfunc TestParseIncomingURL(t *testing.T) {\n\tis := is.New(t"
  },
  {
    "path": "app/main.go",
    "chars": 1541,
    "preview": "package main\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/wailsapp/wails/v2\"\n\t\"github.com/wa"
  },
  {
    "path": "app/menu_parser.go",
    "chars": 2659,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/leaanthony/go-ansi-parser\"\n\t\"github.com/matryer/xbar/p"
  },
  {
    "path": "app/menu_parser_test.go",
    "chars": 3348,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/matryer/is\"\n\t\"github.com/matryer/xba"
  },
  {
    "path": "app/package.json",
    "chars": 3,
    "preview": "{}\n"
  },
  {
    "path": "app/package.sh",
    "chars": 3215,
    "preview": "#!/bin/bash\n\nset -e\n\nVERSION=`git describe --tags`\n\n# usage:\n#   git tag -a v0.1.0 -m \"release tag.\"\n#   git push origin"
  },
  {
    "path": "app/person_service.go",
    "chars": 1311,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\n\t\"github.com/matryer/xbar/pkg/metadata\"\n)\n\n"
  },
  {
    "path": "app/plugins_service.go",
    "chars": 8733,
    "preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"s"
  },
  {
    "path": "app/settings.go",
    "chars": 1837,
    "preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n)\n\ntype se"
  },
  {
    "path": "app/settings_test.go",
    "chars": 474,
    "preview": "package main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/matryer/is\"\n)\n\nfunc TestSettings(t *testing.T) {\n"
  },
  {
    "path": "app/test.sh",
    "chars": 293,
    "preview": "#!/bin/bash\n\nset -e\n\nVERSION=`git describe --tags`\necho -n $VERSION > .version\n\ngo test\n\ncd ../pkg/metadata\ngo test\ncd ."
  },
  {
    "path": "app/wails.json",
    "chars": 253,
    "preview": "{\n    \"name\": \"xbar\",\n    \"outputfilename\": \"xbar\",\n    \"html\": \"frontend/public/index.html\",\n    \"js\": \"frontend/public"
  },
  {
    "path": "archive/bitbar/.gitignore",
    "chars": 212,
    "preview": "# xcode noise\n\nbuild/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n"
  },
  {
    "path": "archive/bitbar/.gitmodules",
    "chars": 788,
    "preview": "[submodule \"App/Vendor/STPrivilegedTask\"]\n\tpath = App/Vendor/STPrivilegedTask\n\turl = https://github.com/sveinbjornt/STPr"
  },
  {
    "path": "archive/bitbar/.travis.yml",
    "chars": 2974,
    "preview": "language: objective-c\nosx_image: xcode11.2\nxcode_project: App/BitBar.xcodeproj\nxcode_scheme: BitBar\nxcode_sdk: macosx10."
  },
  {
    "path": "archive/bitbar/App/BitBar/App.xib",
    "chars": 933,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3"
  },
  {
    "path": "archive/bitbar/App/BitBar/AppDelegate.m",
    "chars": 9558,
    "preview": "//\n//  AppDelegate.m\n//  BitBar\n//\n//  Created by Mat Ryer on 11/12/13.\n//  Copyright (c) 2013 Bit Bar. All rights reser"
  },
  {
    "path": "archive/bitbar/App/BitBar/Base.lproj/MainMenu.xib",
    "chars": 909,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3"
  },
  {
    "path": "archive/bitbar/App/BitBar/BitBar-Info.plist",
    "chars": 1403,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/BitBar/CHANGELOG.md",
    "chars": 3011,
    "preview": "# BitBar Changes\n\n## v1.9.2\n\n* Added Sparkle updater\n\n## v1.9.1\n\n* Bug fixes and performance enhancements\n\n## v1.9\n\n* Re"
  },
  {
    "path": "archive/bitbar/App/BitBar/ExecutablePlugin.h",
    "chars": 357,
    "preview": "//\n//  ExecutablePlugin.h\n//  BitBar\n//\n//  Created by Mathias Leppich on 22/01/14.\n//  Copyright (c) 2014 Bit Bar. All "
  },
  {
    "path": "archive/bitbar/App/BitBar/ExecutablePlugin.m",
    "chars": 4562,
    "preview": "//\n//  ExecutablePlugin.m\n//  BitBar\n//\n//  Created by Mathias Leppich on 22/01/14.\n//  Copyright (c) 2014 Bit Bar. All "
  },
  {
    "path": "archive/bitbar/App/BitBar/HTMLPlugin.h",
    "chars": 672,
    "preview": "//\n//  HTMLPlugin.h\n//  BitBar\n//\n//  Created by Mathias Leppich on 22/01/14.\n//  Copyright (c) 2014 Bit Bar. All rights"
  },
  {
    "path": "archive/bitbar/App/BitBar/HTMLPlugin.m",
    "chars": 9037,
    "preview": "//\n//  HTMLPlugin.m\n//  BitBar\n//\n//  Created by Mathias Leppich on 22/01/14.\n//  Copyright (c) 2014 Bit Bar. All rights"
  },
  {
    "path": "archive/bitbar/App/BitBar/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1270,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"bitbar-16.png\",\n      \"scale\" "
  },
  {
    "path": "archive/bitbar/App/BitBar/NSColor+Hex.h",
    "chars": 301,
    "preview": "//\n//  NSColor+Hex.h\n//  BitBar\n//\n//  Created by Mathias Leppich on 03/02/14.\n//  Copyright (c) 2014 Bit Bar. All right"
  },
  {
    "path": "archive/bitbar/App/BitBar/NSColor+Hex.m",
    "chars": 4663,
    "preview": "//\n//  NSColor+Hex.m\n//  BitBar\n//\n//  Created by Mathias Leppich on 03/02/14.\n//  Copyright (c) 2014 Bit Bar. All right"
  },
  {
    "path": "archive/bitbar/App/BitBar/NSString+ANSI.h",
    "chars": 293,
    "preview": "//\n//  NSString+ANSI.h\n//  BitBar\n//\n//  Created by Kent Karlsson on 3/11/16.\n//  Copyright © 2016 Bit Bar. All rights r"
  },
  {
    "path": "archive/bitbar/App/BitBar/NSString+ANSI.m",
    "chars": 4510,
    "preview": "//\n//  NSString+ANSI.m\n//  BitBar\n//\n//  Created by Kent Karlsson on 3/11/16.\n//  Copyright © 2016 Bit Bar. All rights r"
  },
  {
    "path": "archive/bitbar/App/BitBar/NSUserDefaults+Settings.h",
    "chars": 348,
    "preview": "//\n//  Settings.h\n//  BitBar\n//\n//  Created by Mat Ryer on 11/13/13.\n//  Copyright (c) 2013 Bit Bar. All rights reserved"
  },
  {
    "path": "archive/bitbar/App/BitBar/NSUserDefaults+Settings.m",
    "chars": 1047,
    "preview": "//\n//  Settings.m\n//  BitBar\n//\n//  Created by Mat Ryer on 11/13/13.\n//  Copyright (c) 2013 Bit Bar. All rights reserved"
  },
  {
    "path": "archive/bitbar/App/BitBar/Plugin.h",
    "chars": 1551,
    "preview": "//\n//  Plugin.h\n//  BitBar\n//\n//  Created by Mat Ryer on 11/12/13.\n//  Copyright (c) 2013 Bit Bar. All rights reserved.\n"
  },
  {
    "path": "archive/bitbar/App/BitBar/Plugin.m",
    "chars": 20723,
    "preview": "//\n//  Plugin.m\n//  BitBar\n//\n//  Created by Mat Ryer on 11/12/13.\n//  Copyright (c) 2013 Bit Bar. All rights reserved.\n"
  },
  {
    "path": "archive/bitbar/App/BitBar/PluginManager.h",
    "chars": 875,
    "preview": "//\n//  PluginManager.h\n//  BitBar\n//\n//  Created by Mat Ryer on 11/12/13.\n//  Copyright (c) 2013 Bit Bar. All rights res"
  },
  {
    "path": "archive/bitbar/App/BitBar/PluginManager.m",
    "chars": 10744,
    "preview": "//\n//  PluginManager.m\n//  BitBar\n//\n//  Created by Mat Ryer on 11/12/13.\n//  Copyright (c) 2013 Bit Bar. All rights res"
  },
  {
    "path": "archive/bitbar/App/BitBar/incoming-url-tests.html",
    "chars": 1454,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n\t<title>Incoming URL tests</title>\n</head>\n<body>\n\n<h1>BitBar - Incoming URL tests</h1>\n\n<"
  },
  {
    "path": "archive/bitbar/App/BitBar.xcodeproj/project.pbxproj",
    "chars": 63212,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "archive/bitbar/App/BitBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 151,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:BitBar.xcodepro"
  },
  {
    "path": "archive/bitbar/App/BitBar.xcodeproj/project.xcworkspace/xcshareddata/BitBar.xccheckout",
    "chars": 1478,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/BitBar.xcodeproj/project.xcworkspace/xcshareddata/BitBar.xcscmblueprint",
    "chars": 4193,
    "preview": "{\n  \"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey\" : \"300A25E6089E8365F1B9BCC0BF644BF304850FDB\",\n  \"DVTS"
  },
  {
    "path": "archive/bitbar/App/BitBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/BitBar.xcodeproj/xcshareddata/xcschemes/BitBar.xcscheme",
    "chars": 4670,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1120\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "archive/bitbar/App/BitBar.xcodeproj/xcshareddata/xcschemes/BitBarDistro.xcscheme",
    "chars": 3233,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1120\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "archive/bitbar/App/BitBarTests/BitBarTests-Info.plist",
    "chars": 708,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/BitBarTests/PluginManager+Test.h",
    "chars": 294,
    "preview": "//\n//  PluginManager+Test.h\n//  BitBar\n//\n//  Created by Kent Karlsson on 3/11/16.\n//  Copyright © 2016 Bit Bar. All rig"
  },
  {
    "path": "archive/bitbar/App/BitBarTests/PluginManager+Test.m",
    "chars": 734,
    "preview": "//\n//  PluginManager+Test.m\n//  BitBar\n//\n//  Created by Kent Karlsson on 3/11/16.\n//  Copyright © 2016 Bit Bar. All rig"
  },
  {
    "path": "archive/bitbar/App/BitBarTests/PluginManagerTest.m",
    "chars": 1653,
    "preview": "//\n//  PluginManagerTest.m\n//  BitBar\n//\n//  Created by Mat Ryer on 11/12/13.\n//  Copyright (c) 2013 Bit Bar. All rights"
  },
  {
    "path": "archive/bitbar/App/BitBarTests/PluginTest.m",
    "chars": 13792,
    "preview": "//\n//  PluginTest.m\n//  BitBar\n//\n//  Created by Mat Ryer on 11/12/13.\n//  Copyright (c) 2013 Bit Bar. All rights reserv"
  },
  {
    "path": "archive/bitbar/App/BitBarTests/TestPlugins/one.10s.sh",
    "chars": 175,
    "preview": "#!/bin/bash\n\n#  one.sh - a test BitBar plugin\n#  BitBar\n#\n#  Created by Mat Ryer on 11/12/13.\n#  Copyright (c) 2013 Bit "
  },
  {
    "path": "archive/bitbar/App/BitBarTests/TestPlugins/three.7d.sh",
    "chars": 249,
    "preview": "#!/bin/bash\n\n#  three.sh - this test contains multiple lines with whitespace\n#  BitBar\n#\n#  Created by Mat Ryer on 11/12"
  },
  {
    "path": "archive/bitbar/App/BitBarTests/TestPlugins/two.5m.sh",
    "chars": 195,
    "preview": "#!/bin/bash\n\n#  two.sh\n#  BitBar\n#\n#  Created by Mat Ryer on 11/12/13.\n#  Copyright (c) 2013 Bit Bar. All rights reserve"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/.gitignore",
    "chars": 495,
    "preview": "# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!defau"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxyExampe/AppDelegate.h",
    "chars": 820,
    "preview": "//\n//  AppDelegate.h\n//  AHProxyExampe\n//\n//  Created by Eldon on 11/11/14.\n//  Copyright (c) 2014 Eldon Ahrold. All rig"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxyExampe/AppDelegate.m",
    "chars": 2966,
    "preview": "//\n//  AppDelegate.m\n//  AHProxyExampe\n//\n//  Created by Eldon on 11/11/14.\n//  Copyright (c) 2014 Eldon Ahrold. All rig"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxyExampe/Base.lproj/MainMenu.xib",
    "chars": 65095,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxyExampe/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 903,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"16x16\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : "
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxyExampe/Info.plist",
    "chars": 1100,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxyExampe/main.m",
    "chars": 242,
    "preview": "//\n//  main.m\n//  AHProxyExampe\n//\n//  Created by Eldon on 11/11/14.\n//  Copyright (c) 2014 Eldon Ahrold. All rights res"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings/AHProxy.h",
    "chars": 2148,
    "preview": "// AHProxy.h\n//\n// Copyright (c) 2014 Eldon Ahrold\n//\n// Permission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings/AHProxy.m",
    "chars": 4002,
    "preview": "// AHProxy.m\n//\n// Copyright (c) 2014 Eldon Ahrold\n//\n// Permission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings/AHProxySettings.h",
    "chars": 3396,
    "preview": "// AHProxySettings.h\n//\n// Copyright (c) 2014 Eldon Ahrold\n//\n// Permission is hereby granted, free of charge, to any pe"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings/AHProxySettings.m",
    "chars": 10504,
    "preview": "// AHProxySettings.m\n//\n// Copyright (c) 2014 Eldon Ahrold\n//\n// Permission is hereby granted, free of charge, to any pe"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings/NSTask+useSystemProxies.h",
    "chars": 1548,
    "preview": "//\n//  NSTask+useSystemProxies.h\n//  AHProxySettings\n//\n// Copyright (c) 2014 Eldon Ahrold\n//\n// Permission is hereby gr"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings/NSTask+useSystemProxies.m",
    "chars": 2124,
    "preview": "//\n//  NSTask+useSystemProxies.m\n//  AHProxySettings\n//\n// Copyright (c) 2014 Eldon Ahrold\n//\n// Permission is hereby gr"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings.podspec",
    "chars": 645,
    "preview": "Pod::Spec.new do |spec|\n  spec.name = 'AHProxySettings'\n  spec.version = '0.1.1'\n  spec.platform = :osx\n  spec.license ="
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings.xcodeproj/project.pbxproj",
    "chars": 22930,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 160,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:AHProxySettings"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettings.xcodeproj/xcshareddata/xcschemes/AHProxySettings.xcscheme",
    "chars": 2813,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0600\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettingsTests/AHProxySettingsTest.m",
    "chars": 2880,
    "preview": "//\n//  AHProxySettingsTest.m\n//  AHProxySettings\n//\n//  Created by Eldon on 11/9/14.\n//  Copyright (c) 2014 Eldon Ahrold"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/AHProxySettingsTests/Info.plist",
    "chars": 750,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/LICENSE",
    "chars": 1154,
    "preview": "//  Copyright (c) 2014 Eldon Ahrold ( https://github.com/eahrold/AHLaunchCtl )\n//\n// Permission is hereby granted, free "
  },
  {
    "path": "archive/bitbar/App/Vendor/AHProxySettings/README.md",
    "chars": 1389,
    "preview": "#AHProxySettings\n### Simple lib to access the OSX system proxy settings \n\n```Objective-c\nAHProxySettings  *settings = [["
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/.gitignore",
    "chars": 237,
    "preview": "# Xcode\n.DS_Store\n*/build/*\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspecti"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/.travis.yml",
    "chars": 202,
    "preview": "language: objective-c\n\nbefore_script: \n- gem install xcpretty\n\nscript: \n- xcodebuild -project Tests/DateToolsTests/DateT"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/CREDITS.md",
    "chars": 2640,
    "preview": "Credits\n=======\n\nPortions from NSDate+TimeAgo Originally based on code Christopher Pickslay posted to Forrst. Used with "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTConstants.h",
    "chars": 1705,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTConstants.m",
    "chars": 1585,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTError.h",
    "chars": 1674,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTError.m",
    "chars": 3810,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriod.h",
    "chars": 4761,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriod.m",
    "chars": 22719,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodChain.h",
    "chars": 1880,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodChain.m",
    "chars": 7212,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodCollection.h",
    "chars": 2278,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodCollection.m",
    "chars": 12966,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodGroup.h",
    "chars": 2302,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DTTimePeriodGroup.m",
    "chars": 6087,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/bg.lproj/DateTools.strings",
    "chars": 1742,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"преди %d дена\";\n\n/* No comment provided by engineer. */\n\"%d hour"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/ca.lproj/DateTools.strings",
    "chars": 1695,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"Fa %d dies\";\n\n/* No comment provided by engineer. */\n\"%d hours a"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/cs.lproj/DateTools.strings",
    "chars": 1893,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"Před %d dny\";\n\n/* No comment provided by engineer. */\n\"%d hours "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/cy.lproj/DateTools.strings",
    "chars": 1715,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d diwrnod yn ôl\";\n\n/* No comment provided by engineer. */\n\"%d h"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/da.lproj/DateTools.strings",
    "chars": 1737,
    "preview": "/* No comment provided by engineer. */\n \"%d days ago\" = \"%d dage siden\";\n \n /* No comment provided by engineer. */\n \"%d "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/de.lproj/DateTools.strings",
    "chars": 1935,
    "preview": "/* No comment provided by engineer. */\n \"%d days ago\" = \"Vor %d Tagen\";\n \n /* No comment provided by engineer. */\n \"%d h"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/en.lproj/DateTools.strings",
    "chars": 2089,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d days ago\";\n\n/* No comment provided by engineer. */\n\"%d hours "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/es.lproj/DateTools.strings",
    "chars": 1898,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"Hace %d días\";\n\n/* No comment provided by engineer. */\n\"%d hours"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/eu.lproj/DateTools.strings",
    "chars": 1810,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"Orain dela %d egun\";\n\n/* No comment provided by engineer. */\n\"%d"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/fi.lproj/DateTools.strings",
    "chars": 1759,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d päivää sitten\";\n\n/* No comment provided by engineer. */\n\"%d h"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/fr.lproj/DateTools.strings",
    "chars": 1759,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"Il y a %d jours\";\n\n/* No comment provided by engineer. */\n\"%d ho"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/gu.lproj/DateTools.strings",
    "chars": 2108,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d દિવસ પહેલા\";\n\n/* No comment provided by engineer. */\n\"%d hour"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/he.lproj/DateTools.strings",
    "chars": 1643,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"לפני %d ימים\";\n\n/* No comment provided by engineer. */\n\"%d hours"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/hi.lproj/DateTools.strings",
    "chars": 2065,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d दिन पहले\";\n\n/* No comment provided by engineer. */\n\"%d hours "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/hr.lproj/DateTools.strings",
    "chars": 1123,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d dana\";\n\n/* No comment provided by engineer. */\n\"%d hours ago\""
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/hu.lproj/DateTools.strings",
    "chars": 1662,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d napja\";\n\n/* No comment provided by engineer. */\n\"%d hours ago"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/id.lproj/DateTools.strings",
    "chars": 1752,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d hari yang lalu\";\n\n/* No comment provided by engineer. */\n\"%d "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/is.lproj/DateTools.strings",
    "chars": 1739,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d dögum síðan\";\n\n/* No comment provided by engineer. */\n\"%d hou"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/it.lproj/DateTools.strings",
    "chars": 1705,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d giorni fa\";\n\n/* No comment provided by engineer. */\n\"%d hours"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/ja.lproj/DateTools.strings",
    "chars": 1888,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d日前\";\n\n/* No comment provided by engineer. */\n\"%d hours ago\" = "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/ko.lproj/DateTools.strings",
    "chars": 1514,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d일전\";\n\n/* No comment provided by engineer. */\n\"%d hours ago\" = "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/lv.lproj/DateTools.strings",
    "chars": 756,
    "preview": "\"1 year ago\" = \"Pirms gada\";\n\"1 month ago\" = \"Pirms mēneša\";\n\"1 week ago\" = \"Pirms nedēļas\";\n\"1 day ago\" = \"Pirms dienas"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/ms.lproj/DateTools.strings",
    "chars": 2160,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d hari yang lepas\";\n\n/* No comment provided by engineer. */\n\"%d"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/nb.lproj/DateTools.strings",
    "chars": 2676,
    "preview": "/*\n RULES:\n Assume value for (seconds, hours, minutes, days, weeks, months or years) is XXXY, Y is last digit, XY is las"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/nl.lproj/DateTools.strings",
    "chars": 1735,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d dagen geleden\";\n\n/* No comment provided by engineer. */\n\"%d h"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/pl.lproj/DateTools.strings",
    "chars": 1754,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d dni temu\";\n\n/* No comment provided by engineer. */\n\"%d hours "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/pt-PT.lproj/DateTools.strings",
    "chars": 1714,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d dias atrás\";\n\n/* No comment provided by engineer. */\n\"%d hour"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/pt.lproj/DateTools.strings",
    "chars": 1700,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d dias atrás\";\n\n/* No comment provided by engineer. */\n\"%d hour"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/ro.lproj/DateTools.strings",
    "chars": 2050,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"În urmă cu %d zile\";\n\n/* No comment provided by engineer. */\n\"%d"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/ru.lproj/DateTools.strings",
    "chars": 2978,
    "preview": "/*\n RULES:\n Assume value for (seconds, hours, minutes, days, weeks, months or years) is XXXY, Y is last digit, XY is las"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/sl.lproj/DateTools.strings",
    "chars": 2110,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"pred %d dnevi\";\n\n/* No comment provided by engineer. */\n\"%d hour"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/tr.lproj/DateTools.strings",
    "chars": 1858,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d gün önce\";\n\n/* No comment provided by engineer. */\n\"%d hours "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/uk.lproj/DateTools.strings",
    "chars": 2964,
    "preview": "/*\n RULES:\n Assume value for (seconds, hours, minutes, days, weeks, months or years) is XXXY, Y is last digit, XY is las"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/vi.lproj/DateTools.strings",
    "chars": 1692,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d ngày trước\";\n\n/* No comment provided by engineer. */\n\"%d hour"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/zh-Hans.lproj/DateTools.strings",
    "chars": 1807,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d天前\";\n\n/* No comment provided by engineer. */\n\"%d hours ago\" = "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.bundle/zh-Hant.lproj/DateTools.strings",
    "chars": 1703,
    "preview": "/* No comment provided by engineer. */\n\"%d days ago\" = \"%d天前\";\n\n/* No comment provided by engineer. */\n\"%d hours ago\" = "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/DateTools.h",
    "chars": 1312,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/NSDate+DateTools.h",
    "chars": 7456,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools/NSDate+DateTools.m",
    "chars": 59750,
    "preview": "// Copyright (C) 2014 by Matthew York\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining a c"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/DateTools.podspec",
    "chars": 720,
    "preview": "Pod::Spec.new do |s|\n  s.name         = 'DateTools'\n  s.version      = '1.7.0'\n  s.summary      = 'Dates and time made e"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateTools/Info.plist",
    "chars": 822,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/AppDelegate.h",
    "chars": 300,
    "preview": "//\n//  AppDelegate.h\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/19/14.\n//\n//\n\n#import <UIKit/UIKit.h>\n\n@in"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/AppDelegate.m",
    "chars": 3316,
    "preview": "//\n//  AppDelegate.m\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/19/14.\n//\n//\n\n#import \"AppDelegate.h\"\n#imp"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/Colours.h",
    "chars": 12754,
    "preview": "// Copyright (C) 2013 by Benjamin Gordon\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/Colours.m",
    "chars": 35580,
    "preview": "// Copyright (C) 2013 by Benjamin Gordon\n//\n// Permission is hereby granted, free of charge, to any\n// person obtaining "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/DateToolsExample-Info.plist",
    "chars": 1058,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/DateToolsExample-Prefix.pch",
    "chars": 344,
    "preview": "//\n//  Prefix header\n//\n//  The contents of this file are implicitly included at the beginning of every source file.\n//\n"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/DateToolsViewController.h",
    "chars": 189,
    "preview": "//\n//  DateToolsViewController.h\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/22/14.\n//\n//\n\n#import <UIKit/U"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/DateToolsViewController.m",
    "chars": 4070,
    "preview": "//\n//  DateToolsViewController.m\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/22/14.\n//\n//\n\n#import \"DateToo"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/DateToolsViewController.xib",
    "chars": 15493,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/ExampleNavigationController.h",
    "chars": 203,
    "preview": "//\n//  ExampleNavigationController.h\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/22/14.\n//\n//\n\n#import <UIK"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/ExampleNavigationController.m",
    "chars": 1277,
    "preview": "//\n//  ExampleNavigationController.m\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/22/14.\n//\n//\n\n#import \"Exa"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/Images.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 333,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"iphone\",\n      \"size\" : \"29x29\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\""
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/Images.xcassets/LaunchImage.launchimage/Contents.json",
    "chars": 442,
    "preview": "{\n  \"images\" : [\n    {\n      \"orientation\" : \"portrait\",\n      \"idiom\" : \"iphone\",\n      \"extent\" : \"full-screen\",\n     "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/TimePeriodsViewController.h",
    "chars": 193,
    "preview": "//\n//  TimePeriodsViewController.h\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/22/14.\n//\n//\n\n#import <UIKit"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/TimePeriodsViewController.m",
    "chars": 7992,
    "preview": "//\n//  TimePeriodsViewController.m\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/22/14.\n//\n//\n\n#import \"TimeP"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/TimePeriodsViewController.xib",
    "chars": 16057,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" versi"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/en.lproj/InfoPlist.strings",
    "chars": 45,
    "preview": "/* Localized versions of Info.plist keys */\n\n"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample/main.m",
    "chars": 293,
    "preview": "//\n//  main.m\n//  DateToolsExample\n//\n//  Created by Matthew York on 3/19/14.\n//\n//\n\n#import <UIKit/UIKit.h>\n\n#import \"A"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample.xcodeproj/project.pbxproj",
    "chars": 46372,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 161,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:DateToolsExampl"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExample.xcodeproj/xcshareddata/xcschemes/DateTools.xcscheme",
    "chars": 4212,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0640\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExampleTests/DateToolsExampleTests-Info.plist",
    "chars": 692,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "archive/bitbar/App/Vendor/DateTools/Examples/DateToolsExample/DateToolsExampleTests/en.lproj/InfoPlist.strings",
    "chars": 45,
    "preview": "/* Localized versions of Info.plist keys */\n\n"
  }
]

// ... and 508 more files (download for full content)

About this extraction

This page contains the full source code of the matryer/xbar GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 708 files (4.6 MB), approximately 1.2M tokens, and a symbol index with 364 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!