├── .github
├── icons
│ ├── contributed-flame-mac-white.svg
│ ├── contributed-flame-mac.svg
│ ├── contributed-octocat-mac-white.svg
│ ├── contributed-octocat-mac.svg
│ ├── contributed-tile-mac-white.svg
│ ├── contributed-tile-mac.svg
│ ├── icon.svg
│ ├── pending-flame-mac-white.svg
│ ├── pending-flame-mac.svg
│ ├── pending-octocat-mac-white.svg
│ ├── pending-octocat-mac.svg
│ ├── pending-tile-mac-white.svg
│ ├── pending-tile-mac.svg
│ ├── screenshot.png
│ ├── streaking-flame-mac-white.svg
│ ├── streaking-flame-mac.svg
│ ├── streaking-octocat-mac-white.svg
│ ├── streaking-octocat-mac.svg
│ ├── streaking-tile-mac-white.svg
│ └── streaking-tile-mac.svg
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .husky
├── commit-msg
├── pre-commit
└── pre-push
├── CHANGELOG.md
├── LICENSE
├── README.md
├── biome.json
├── forge.config.ts
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── icons
│ ├── app
│ │ ├── icon.icns
│ │ └── icon.ico
│ ├── index.ts
│ ├── linux
│ │ ├── flame
│ │ │ ├── contributed.png
│ │ │ ├── error.png
│ │ │ ├── pending.png
│ │ │ └── streaking.png
│ │ ├── octocat
│ │ │ ├── contributed.png
│ │ │ ├── error.png
│ │ │ ├── pending.png
│ │ │ └── streaking.png
│ │ └── tile
│ │ │ ├── contributed.png
│ │ │ ├── error.png
│ │ │ ├── pending.png
│ │ │ └── streaking.png
│ ├── macos
│ │ ├── flame
│ │ │ ├── contributed@2x.png
│ │ │ ├── error@2x.png
│ │ │ ├── pending@2x.png
│ │ │ └── streaking@2x.png
│ │ ├── octocat
│ │ │ ├── contributed@2x.png
│ │ │ ├── error@2x.png
│ │ │ ├── pending@2x.png
│ │ │ └── streaking@2x.png
│ │ └── tile
│ │ │ ├── contributed@2x.png
│ │ │ ├── error@2x.png
│ │ │ ├── pending@2x.png
│ │ │ └── streaking@2x.png
│ └── windows
│ │ ├── flame
│ │ ├── contributed.ico
│ │ ├── error.ico
│ │ ├── pending.ico
│ │ └── streaking.ico
│ │ ├── octocat
│ │ ├── contributed.ico
│ │ ├── error.ico
│ │ ├── pending.ico
│ │ └── streaking.ico
│ │ └── tile
│ │ ├── contributed.ico
│ │ ├── error.ico
│ │ ├── pending.ico
│ │ └── streaking.ico
├── main
│ ├── logger.ts
│ ├── main.ts
│ ├── menu.ts
│ ├── store.ts
│ └── tray-menu.ts
└── renderer
│ ├── index.html
│ ├── preload.ts
│ ├── renderer.ts
│ └── styles.css
├── tailwind.config.js
├── tsconfig.json
├── webpack.main.config.ts
├── webpack.plugins.ts
├── webpack.renderer.config.ts
└── webpack.rules.ts
/.github/icons/contributed-flame-mac-white.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.github/icons/contributed-flame-mac.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.github/icons/contributed-octocat-mac-white.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.github/icons/contributed-octocat-mac.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.github/icons/contributed-tile-mac-white.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.github/icons/contributed-tile-mac.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.github/icons/icon.svg:
--------------------------------------------------------------------------------
1 |
46 |
--------------------------------------------------------------------------------
/.github/icons/pending-flame-mac-white.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.github/icons/pending-flame-mac.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.github/icons/pending-octocat-mac-white.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/icons/pending-octocat-mac.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/icons/pending-tile-mac-white.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.github/icons/pending-tile-mac.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.github/icons/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/.github/icons/screenshot.png
--------------------------------------------------------------------------------
/.github/icons/streaking-flame-mac-white.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/icons/streaking-flame-mac.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/.github/icons/streaking-octocat-mac-white.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/.github/icons/streaking-octocat-mac.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/.github/icons/streaking-tile-mac-white.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/.github/icons/streaking-tile-mac.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v4
17 | - uses: actions/setup-node@v4
18 | with:
19 | node-version: 22.x
20 | - run: npm clean-install
21 | - run: npm run lint
22 | - run: npm run package # build and package
23 |
24 | release:
25 | needs: build
26 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
27 | runs-on: ubuntu-latest
28 | permissions:
29 | contents: write # to be able to publish a GitHub release
30 | issues: write # to be able to comment on released issues
31 | pull-requests: write # to be able to comment on released pull requests
32 | id-token: write # to enable use of OIDC for npm provenance
33 | steps:
34 | - uses: actions/checkout@v4
35 | - uses: actions/setup-node@v4
36 | with:
37 | node-version: 22.x
38 | - run: npm clean-install
39 | - run: npm audit signatures
40 | - run: npx semantic-release
41 | env:
42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43 | HUSKY: 0
44 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on: workflow_dispatch
4 |
5 |
6 | permissions:
7 | contents: write
8 |
9 | jobs:
10 | release-linux:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-node@v4
15 | with:
16 | node-version: 22.x
17 | - run: npm clean-install --include=dev
18 | - run: npm run publish
19 | env:
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 |
22 | release-mac:
23 | runs-on: macos-latest
24 | steps:
25 | - uses: actions/checkout@v4
26 | - uses: actions/setup-node@v4
27 | with:
28 | node-version: 22.x
29 | - run: npm clean-install --include=dev
30 | - run: npm run publish
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 |
34 | release-windows:
35 | runs-on: windows-latest
36 | steps:
37 | - uses: actions/checkout@v4
38 | - uses: actions/setup-node@v4
39 | with:
40 | node-version: 22.x
41 | - run: npm clean-install --include=dev
42 | - run: npm run publish
43 | env:
44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 | .DS_Store
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # TypeScript cache
43 | *.tsbuildinfo
44 |
45 | # Optional npm cache directory
46 | .npm
47 |
48 | # Optional eslint cache
49 | .eslintcache
50 |
51 | # Optional REPL history
52 | .node_repl_history
53 |
54 | # Output of 'npm pack'
55 | *.tgz
56 |
57 | # Yarn Integrity file
58 | .yarn-integrity
59 |
60 | # dotenv environment variables file
61 | .env
62 | .env.test
63 |
64 | # parcel-bundler cache (https://parceljs.org/)
65 | .cache
66 |
67 | # next.js build output
68 | .next
69 |
70 | # nuxt.js build output
71 | .nuxt
72 |
73 | # vuepress build output
74 | .vuepress/dist
75 |
76 | # Serverless directories
77 | .serverless/
78 |
79 | # FuseBox cache
80 | .fusebox/
81 |
82 | # DynamoDB Local files
83 | .dynamodb/
84 |
85 | # Webpack
86 | .webpack/
87 |
88 | # Vite
89 | .vite/
90 |
91 | # Electron-Forge
92 | out/
93 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | npx --no -- commitlint --edit "$1"
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | npx --no -- lint-staged
2 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | npm run lint && npm run package
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [2.6.1](https://github.com/jamieweavis/streaker/compare/v2.6.0...v2.6.1) (2025-04-14)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * update description ([36ce895](https://github.com/jamieweavis/streaker/commit/36ce8952587f1879ca7dfbb44ab74ceeab92a8d8))
7 |
8 | # [2.6.0](https://github.com/jamieweavis/streaker/compare/v2.5.2...v2.6.0) (2025-03-29)
9 |
10 |
11 | ### Features
12 |
13 | * add at risk indicator to previous streak menu item ([3c2571d](https://github.com/jamieweavis/streaker/commit/3c2571d67a6c5d8297d9fad0f6a33a4cf362ceda))
14 |
15 | ## [2.5.2](https://github.com/jamieweavis/streaker/compare/v2.5.1...v2.5.2) (2025-03-24)
16 |
17 |
18 | ### Bug Fixes
19 |
20 | * upgrade to contribution v7 ([3aa7144](https://github.com/jamieweavis/streaker/commit/3aa7144309394f028d8cce8e429ee4b641127151))
21 |
22 | ## [2.5.1](https://github.com/jamieweavis/streaker/compare/v2.5.0...v2.5.1) (2025-03-20)
23 |
24 |
25 | ### Bug Fixes
26 |
27 | * reminder notification checkbox default state ([83b0de8](https://github.com/jamieweavis/streaker/commit/83b0de885aa87a63422a6b2f5c5c930d655d5729))
28 |
29 | # [2.5.0](https://github.com/jamieweavis/streaker/compare/v2.4.3...v2.5.0) (2025-03-20)
30 |
31 |
32 | ### Features
33 |
34 | * open preferences window when app is reopened and already running ([d00b086](https://github.com/jamieweavis/streaker/commit/d00b086372b43aeb635fe31b989dd520a8f3c1c0))
35 |
36 | ## [2.4.3](https://github.com/jamieweavis/streaker/compare/v2.4.2...v2.4.3) (2025-03-18)
37 |
38 |
39 | ### Bug Fixes
40 |
41 | * increase preferences window width to avoid line wrap which causes scroll ([775c716](https://github.com/jamieweavis/streaker/commit/775c716f783c5cf4f9ab8a1fdc75a16609b34d56))
42 |
43 | ## [2.4.2](https://github.com/jamieweavis/streaker/compare/v2.4.1...v2.4.2) (2025-03-18)
44 |
45 |
46 | ### Bug Fixes
47 |
48 | * add linux specific tray icons ([673d9b0](https://github.com/jamieweavis/streaker/commit/673d9b0eaa31eff51182dfb4d8481a6fa5c0e3e3))
49 |
50 | ## [2.4.1](https://github.com/jamieweavis/streaker/compare/v2.4.0...v2.4.1) (2025-03-17)
51 |
52 |
53 | ### Bug Fixes
54 |
55 | * tile icon theme alpha issue ([abc3797](https://github.com/jamieweavis/streaker/commit/abc37978d2b36856ae8eab5216682e07519309e3))
56 |
57 | # [2.4.0](https://github.com/jamieweavis/streaker/compare/v2.3.3...v2.4.0) (2025-03-17)
58 |
59 |
60 | ### Features
61 |
62 | * improve tile icon theme to be simpler & clearer ([ed5e361](https://github.com/jamieweavis/streaker/commit/ed5e361aac4f299402d7ce4131dcaa6673cc9dc6))
63 |
64 | ## [2.3.3](https://github.com/jamieweavis/streaker/compare/v2.3.2...v2.3.3) (2025-03-15)
65 |
66 |
67 | ### Bug Fixes
68 |
69 | * test executable name fix for linux ([9dfd176](https://github.com/jamieweavis/streaker/commit/9dfd176119a54de2f8682abd540f2263a33646e2))
70 |
71 | ## [2.3.3](https://github.com/jamieweavis/streaker/compare/v2.3.2...v2.3.3) (2025-03-15)
72 |
73 |
74 | ### Bug Fixes
75 |
76 | * test executable name fix for linux ([9dfd176](https://github.com/jamieweavis/streaker/commit/9dfd176119a54de2f8682abd540f2263a33646e2))
77 |
78 | ## [2.3.2](https://github.com/jamieweavis/streaker/compare/v2.3.1...v2.3.2) (2025-03-15)
79 |
80 |
81 | ### Bug Fixes
82 |
83 | * executable name capitalisation ([8ac19b6](https://github.com/jamieweavis/streaker/commit/8ac19b6049c335901567874f28492fd5c56702dc))
84 |
85 | ## [2.3.1](https://github.com/jamieweavis/streaker/compare/v2.3.0...v2.3.1) (2025-03-15)
86 |
87 |
88 | ### Bug Fixes
89 |
90 | * add initial linux icons ([cdda1aa](https://github.com/jamieweavis/streaker/commit/cdda1aadebb4002e98ee0e85be2efedb446e4e69))
91 |
92 | # [2.3.0](https://github.com/jamieweavis/streaker/compare/v2.2.0...v2.3.0) (2025-03-15)
93 |
94 |
95 | ### Features
96 |
97 | * add auto update via github releases ([95b7958](https://github.com/jamieweavis/streaker/commit/95b79581277a74abd815d6dee8206645aac72c81))
98 |
99 | # [2.2.0](https://github.com/jamieweavis/streaker/compare/v2.1.3...v2.2.0) (2025-03-15)
100 |
101 |
102 | ### Features
103 |
104 | * add better error handling & logging ([4f4ab7c](https://github.com/jamieweavis/streaker/commit/4f4ab7c8b21a473a14af6fa49c10a507ff6a9e38))
105 | * add logging with electron-log ([6e026ee](https://github.com/jamieweavis/streaker/commit/6e026eef4f43e0cf4eae2e447fd7d7b3bb7ec09a))
106 |
107 | ## [2.1.3](https://github.com/jamieweavis/streaker/compare/v2.1.2...v2.1.3) (2025-03-14)
108 |
109 |
110 | ### Bug Fixes
111 |
112 | * dev tools auto open in dev mode ([25f634c](https://github.com/jamieweavis/streaker/commit/25f634c309898ecfda9385262a3c54fb01f7a71c))
113 |
114 | ## [2.1.2](https://github.com/jamieweavis/streaker/compare/v2.1.1...v2.1.2) (2025-03-14)
115 |
116 |
117 | ### Bug Fixes
118 |
119 | * add missing light mode button styles ([e27e2cb](https://github.com/jamieweavis/streaker/commit/e27e2cbb8310aefad2e40cf2cf376abade65ac0f))
120 |
121 | ## [2.1.1](https://github.com/jamieweavis/streaker/compare/v2.1.0...v2.1.1) (2025-03-14)
122 |
123 |
124 | ### Bug Fixes
125 |
126 | * tidy tooltip & reduce window width ([f22e54a](https://github.com/jamieweavis/streaker/commit/f22e54a4b1d837c8a03ed4ccef82e7d970b8a341))
127 |
128 | # [2.1.0](https://github.com/jamieweavis/streaker/compare/v2.0.0...v2.1.0) (2025-03-14)
129 |
130 |
131 | ### Bug Fixes
132 |
133 | * always refetch contribution stats on preferences save ([e17c4b4](https://github.com/jamieweavis/streaker/commit/e17c4b4bcdd99aa0d9338db76297480a2a3bb187))
134 | * rename contributions best to most ([e61278f](https://github.com/jamieweavis/streaker/commit/e61278f02e659a4b418301033bbab3c61c960030))
135 |
136 |
137 | ### Features
138 |
139 | * improve tile icon theme ([12917d5](https://github.com/jamieweavis/streaker/commit/12917d5bd03d0c9295fc0a6f4a9814617669602e))
140 |
141 | # [2.0.0](https://github.com/jamieweavis/streaker/compare/v1.6.1...v2.0.0) (2025-03-13)
142 |
143 |
144 | * refactor!: upgrade node & tooling, add electron forge, remove react ([2895318](https://github.com/jamieweavis/streaker/commit/2895318cff8996a2e808d9a37fe305bc42e92813))
145 |
146 |
147 | ### BREAKING CHANGES
148 |
149 | * Upgrade Node.js to LTS (22)
150 |
151 | ## [1.6.1](https://github.com/jamieweavis/streaker/compare/v1.6.0...v1.6.1) (2023-08-29)
152 |
153 |
154 | ### Bug Fixes
155 |
156 | * streak notification message ([ceec31e](https://github.com/jamieweavis/streaker/commit/ceec31ec8c77ecd0a72130fd373d96a9584425d4))
157 |
158 | # [1.6.0](https://github.com/jamieweavis/streaker/compare/v1.5.1...v1.6.0) (2023-03-24)
159 |
160 |
161 | ### Features
162 |
163 | * add streak count to notification and only show if streaking ([6669aae](https://github.com/jamieweavis/streaker/commit/6669aaeb541483d73c63b54f12221b255f377084)), closes [#263](https://github.com/jamieweavis/streaker/issues/263)
164 |
165 | ## [1.5.1](https://github.com/jamieweavis/streaker/compare/v1.5.0...v1.5.1) (2023-03-15)
166 |
167 |
168 | ### Bug Fixes
169 |
170 | * enable remote window module ([d36f3df](https://github.com/jamieweavis/streaker/commit/d36f3df42ec82e0c6af2ab9b93b91d59829c3547))
171 | * update contribution to fix stat counts ([8405c6e](https://github.com/jamieweavis/streaker/commit/8405c6e8db0bb73a56bb5e128233562fb95c5e96))
172 |
173 | # [1.5.0](https://github.com/jamieweavis/streaker/compare/v1.4.3...v1.5.0) (2022-01-06)
174 |
175 |
176 | ### Bug Fixes
177 |
178 | * **deps:** update dependency @primer/components to v23 ([854c545](https://github.com/jamieweavis/streaker/commit/854c5454b0f07c7bfbe21a49b665a5cc97b49f6b))
179 | * **deps:** update dependency electron-store to v7 ([f3718ac](https://github.com/jamieweavis/streaker/commit/f3718acea483a4193e12a67f7967bfd942840da4))
180 | * **deps:** update react monorepo to v17 ([2558f8c](https://github.com/jamieweavis/streaker/commit/2558f8c7b11ea797fd631eaad8c750102d8dd2cb))
181 | * adjust window size to fit content ([77e96de](https://github.com/jamieweavis/streaker/commit/77e96dec5bae40221247ddb6afbe524850049e60))
182 | * resolve svg import error and ignore jsx issue ([0ea1420](https://github.com/jamieweavis/streaker/commit/0ea1420b893706ec0e90463f19a6064a992121be))
183 |
184 |
185 | ### Features
186 |
187 | * show crown icon when streaking ([20b0701](https://github.com/jamieweavis/streaker/commit/20b07019b6669a78e291871ba2815d38f952f45d))
188 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-present Jamie Weavis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Streaker
2 |
3 | Cross-platform GitHub contribution streak/statistic tracking menu bar application with reminder notification
4 |
5 | [](https://github.com/jamieweavis/streaker/actions)
6 | [](https://github.com/jamieweavis/streaker/releases)
7 | [](https://github.com/jamieweavis/streaker/releases)
8 | [](https://github.com/jamieweavis/streaker/blob/main/LICENSE)
9 |
10 |
11 |
12 | ## Install
13 |
14 | Download the latest version of Streaker from the **[GitHub releases](https://github.com/jamieweavis/streaker/releases)** page. Or see the **[Building Locally](#building-locally)** section to build it yourself.
15 |
16 | ## Features
17 |
18 | - Menu bar contribution stats
19 | - Streak
20 | - Best
21 | - Current
22 | - Previous
23 | - Contributions
24 | - Most (day)
25 | - Today
26 | - Total (year)
27 | - Menu bar icon changes depending on contribution status
28 | - Empty - you haven't contributed today
29 | - Filled - you have contributed today
30 | - Crown - you're currently on your best streak
31 | - Three different menu bar icon themes
32 | - Flame
33 | - Tile
34 | - Octocat
35 | - Reminder notification so you don't lose your streak
36 |
37 | ### Menu Bar Icons
38 |
39 | #### Light Mode
40 |
41 | | State | Flame | Tile | Octocat |
42 | | ----- | ----- | ---- | ------- |
43 | | Pending |
|
|
|
44 | | Contributed |
|
|
|
45 | | Streaking |
|
|
|
46 |
47 | #### Dark Mode
48 |
49 | | State | Flame | Tile | Octocat |
50 | | ----- | ----- | ---- | ------- |
51 | | Pending |
|
|
|
52 | | Contributed |
|
|
|
53 | | Streaking |
|
|
|
54 |
55 | ## Building Locally
56 |
57 | The app is built & deployed for all platforms via GitHub Actions automatically, but you can build it locally for your platform with the following steps:
58 |
59 | Clone the repository
60 | ```bash
61 | git clone https://github.com/jamieweavis/streaker.git
62 |
63 | cd streaker
64 | ```
65 |
66 | Install npm dependencies (recommended to use Node.js v22)
67 | ```bash
68 | npm install
69 | ```
70 |
71 | Package the app for your current platform
72 | ```bash
73 | npm run package
74 | ```
75 |
76 | The packaged app will now be in the `out/` directory
77 |
78 | ## Development
79 |
80 | Run the app in development mode
81 | ```bash
82 | npm run start
83 | ```
84 |
85 | *Only the renderer process currently supports hot reloading, changes in `src/main/` will require a restart*
86 |
87 | ## Built with
88 |
89 | - [TypeScript](https://github.com/microsoft/TypeScript)
90 | - [Electron](https://github.com/electron/electron)
91 | - [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss)
92 | - [Electron Forge](https://github.com/electron/forge)
93 |
94 | ## Related
95 |
96 | - [Streaker CLI](https://github.com/jamieweavis/streaker-cli) - GitHub contribution streak/statistic tracking command line application with ASCII contribution graph
97 | - [Contribution](https://github.com/jamieweavis/contribution) - GitHub contribution graph parser - contribution streak & statistic calculator with zero dependencies
98 |
99 | ## License
100 |
101 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
102 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "vcs": {
4 | "enabled": false,
5 | "clientKind": "git",
6 | "useIgnoreFile": false
7 | },
8 | "files": {
9 | "ignoreUnknown": false,
10 | "ignore": []
11 | },
12 | "formatter": {
13 | "enabled": true,
14 | "indentStyle": "space",
15 | "indentWidth": 2
16 | },
17 | "organizeImports": {
18 | "enabled": true
19 | },
20 | "linter": {
21 | "enabled": true,
22 | "rules": {
23 | "recommended": true
24 | }
25 | },
26 | "javascript": {
27 | "formatter": {
28 | "quoteStyle": "single"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/forge.config.ts:
--------------------------------------------------------------------------------
1 | import type { ForgeConfig } from '@electron-forge/shared-types';
2 | import { MakerSquirrel } from '@electron-forge/maker-squirrel';
3 | import { MakerZIP } from '@electron-forge/maker-zip';
4 | import { MakerDeb } from '@electron-forge/maker-deb';
5 | import { MakerRpm } from '@electron-forge/maker-rpm';
6 | import { MakerDMG } from '@electron-forge/maker-dmg';
7 | import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
8 | import { WebpackPlugin } from '@electron-forge/plugin-webpack';
9 | import { FusesPlugin } from '@electron-forge/plugin-fuses';
10 | import { FuseV1Options, FuseVersion } from '@electron/fuses';
11 |
12 | import { mainConfig } from './webpack.main.config';
13 | import { rendererConfig } from './webpack.renderer.config';
14 |
15 | const config: ForgeConfig = {
16 | packagerConfig: {
17 | asar: true,
18 | icon: './src/icons/app/icon',
19 | appCategoryType: 'public.app-category.developer-tools',
20 | extraResource: ['./src/'],
21 | executableName: 'streaker',
22 | },
23 | rebuildConfig: {},
24 | makers: [
25 | new MakerSquirrel({}),
26 | new MakerZIP({}, ['darwin']),
27 | new MakerRpm({}),
28 | new MakerDeb({}),
29 | new MakerDMG({}, ['darwin']),
30 | ],
31 | publishers: [
32 | {
33 | name: '@electron-forge/publisher-github',
34 | config: {
35 | repository: {
36 | owner: 'jamieweavis',
37 | name: 'streaker',
38 | },
39 | },
40 | },
41 | ],
42 | plugins: [
43 | new AutoUnpackNativesPlugin({}),
44 | new WebpackPlugin({
45 | mainConfig,
46 | renderer: {
47 | config: rendererConfig,
48 | entryPoints: [
49 | {
50 | html: './src/renderer/index.html',
51 | js: './src/renderer/renderer.ts',
52 | name: 'main_window',
53 | preload: {
54 | js: './src/renderer/preload.ts',
55 | },
56 | },
57 | ],
58 | },
59 | }),
60 | // Fuses are used to enable/disable various Electron functionality
61 | // at package time, before code signing the application
62 | new FusesPlugin({
63 | version: FuseVersion.V1,
64 | [FuseV1Options.RunAsNode]: false,
65 | [FuseV1Options.EnableCookieEncryption]: true,
66 | [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
67 | [FuseV1Options.EnableNodeCliInspectArguments]: false,
68 | [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
69 | [FuseV1Options.OnlyLoadAppFromAsar]: true,
70 | }),
71 | ],
72 | };
73 |
74 | export default config;
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "streaker",
3 | "productName": "Streaker",
4 | "private": true,
5 | "description": "Cross-platform GitHub contribution streak/statistic tracking menu bar application with reminder notification",
6 | "version": "2.6.1",
7 | "homepage": "https://github.com/jamieweavis/streaker#readme",
8 | "license": "MIT",
9 | "main": ".webpack/main",
10 | "author": "Jamie Weavis",
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/jamieweavis/streaker.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/jamieweavis/streaker/issues"
17 | },
18 | "keywords": [
19 | "contribution",
20 | "contribution-graph",
21 | "electron",
22 | "electron-app",
23 | "github",
24 | "macos",
25 | "menubar",
26 | "streak"
27 | ],
28 | "scripts": {
29 | "start": "electron-forge start",
30 | "package": "electron-forge package",
31 | "make": "electron-forge make",
32 | "publish": "electron-forge publish",
33 | "lint": "biome check src",
34 | "lint:fix": "npm run lint -- --write",
35 | "format": "biome format src",
36 | "format:write": "npm run format -- --write",
37 | "prepare": "husky"
38 | },
39 | "release": {
40 | "branches": [
41 | "main"
42 | ],
43 | "plugins": [
44 | "@semantic-release/changelog",
45 | "@semantic-release/commit-analyzer",
46 | "@semantic-release/npm",
47 | "@semantic-release/git",
48 | "@semantic-release/github",
49 | "@semantic-release/release-notes-generator"
50 | ]
51 | },
52 | "lint-staged": {
53 | "*.ts": [
54 | "biome format --write",
55 | "biome lint --write"
56 | ]
57 | },
58 | "commitlint": {
59 | "extends": [
60 | "@commitlint/config-conventional"
61 | ]
62 | },
63 | "devDependencies": {
64 | "@biomejs/biome": "^1.9.4",
65 | "@commitlint/cli": "^19.8.0",
66 | "@commitlint/config-conventional": "^19.8.0",
67 | "@electron-forge/cli": "^7.7.0",
68 | "@electron-forge/maker-deb": "^7.7.0",
69 | "@electron-forge/maker-dmg": "^7.7.0",
70 | "@electron-forge/maker-rpm": "^7.7.0",
71 | "@electron-forge/maker-squirrel": "^7.7.0",
72 | "@electron-forge/maker-zip": "^7.7.0",
73 | "@electron-forge/plugin-auto-unpack-natives": "^7.7.0",
74 | "@electron-forge/plugin-fuses": "^7.7.0",
75 | "@electron-forge/plugin-webpack": "^7.7.0",
76 | "@electron-forge/publisher-github": "^7.7.0",
77 | "@electron/fuses": "^1.8.0",
78 | "@semantic-release/changelog": "6.0.3",
79 | "@semantic-release/commit-analyzer": "13.0.1",
80 | "@semantic-release/git": "10.0.1",
81 | "@semantic-release/github": "11.0.1",
82 | "@semantic-release/npm": "12.0.1",
83 | "@semantic-release/release-notes-generator": "14.0.3",
84 | "@types/auto-launch": "^5.0.5",
85 | "@vercel/webpack-asset-relocator-loader": "^1.7.4",
86 | "css-loader": "^7.1.2",
87 | "electron": "35.0.1",
88 | "fork-ts-checker-webpack-plugin": "^9.0.2",
89 | "husky": "^9.1.7",
90 | "lint-staged": "^15.5.0",
91 | "node-loader": "^2.1.0",
92 | "postcss-loader": "^8.1.1",
93 | "semantic-release": "24.2.3",
94 | "style-loader": "^4.0.0",
95 | "ts-loader": "^9.5.2",
96 | "ts-node": "^10.9.2",
97 | "typescript": "^5.8.2"
98 | },
99 | "dependencies": {
100 | "@tailwindcss/postcss": "4.0.14",
101 | "auto-launch": "^5.0.6",
102 | "contribution": "^7.0.0",
103 | "cron": "^4.1.0",
104 | "electron-log": "^5.3.2",
105 | "electron-squirrel-startup": "^1.0.1",
106 | "electron-store": "^8.2.0",
107 | "postcss": "^8.5.3",
108 | "tailwindcss": "4.0.14",
109 | "update-electron-app": "^3.1.1"
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | '@tailwindcss/postcss': {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/src/icons/app/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/app/icon.icns
--------------------------------------------------------------------------------
/src/icons/app/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/app/icon.ico
--------------------------------------------------------------------------------
/src/icons/index.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'node:path';
2 | import { type NativeImage, app, nativeImage } from 'electron';
3 |
4 | export interface Icon {
5 | contributed: NativeImage;
6 | error: NativeImage;
7 | pending: NativeImage;
8 | streaking: NativeImage;
9 | }
10 |
11 | export interface Icons {
12 | [theme: string]: Icon;
13 | }
14 |
15 | export let icons: Icons;
16 |
17 | const appPath = app.getAppPath().replace('app.asar', '');
18 |
19 | // macOS Flame
20 | const macosFlameContributed = nativeImage.createFromPath(
21 | join(appPath, 'src/icons/macos/flame/contributed@2x.png'),
22 | );
23 | macosFlameContributed.setTemplateImage(true);
24 | const macosFlameError = nativeImage.createFromPath(
25 | join(appPath, 'src/icons/macos/flame/error@2x.png'),
26 | );
27 | macosFlameError.setTemplateImage(true);
28 | const macosFlamePending = nativeImage.createFromPath(
29 | join(appPath, 'src/icons/macos/flame/pending@2x.png'),
30 | );
31 | macosFlamePending.setTemplateImage(true);
32 | const macosFlameStreaking = nativeImage.createFromPath(
33 | join(appPath, 'src/icons/macos/flame/streaking@2x.png'),
34 | );
35 | macosFlameStreaking.setTemplateImage(true);
36 |
37 | // macOS Tile
38 | const macosTileContributed = nativeImage.createFromPath(
39 | join(appPath, 'src/icons/macos/tile/contributed@2x.png'),
40 | );
41 | macosTileContributed.setTemplateImage(true);
42 | const macosTileError = nativeImage.createFromPath(
43 | join(appPath, 'src/icons/macos/tile/error@2x.png'),
44 | );
45 | macosTileError.setTemplateImage(true);
46 | const macosTilePending = nativeImage.createFromPath(
47 | join(appPath, 'src/icons/macos/tile/pending@2x.png'),
48 | );
49 | macosTilePending.setTemplateImage(true);
50 | const macosTileStreaking = nativeImage.createFromPath(
51 | join(appPath, 'src/icons/macos/tile/streaking@2x.png'),
52 | );
53 | macosTileStreaking.setTemplateImage(true);
54 |
55 | // macOS Octocat
56 | const macosOctocatContributed = nativeImage.createFromPath(
57 | join(appPath, 'src/icons/macos/octocat/contributed@2x.png'),
58 | );
59 | macosOctocatContributed.setTemplateImage(true);
60 | const macosOctocatError = nativeImage.createFromPath(
61 | join(appPath, 'src/icons/macos/octocat/error@2x.png'),
62 | );
63 | macosOctocatError.setTemplateImage(true);
64 | const macosOctocatPending = nativeImage.createFromPath(
65 | join(appPath, 'src/icons/macos/octocat/pending@2x.png'),
66 | );
67 | macosOctocatPending.setTemplateImage(true);
68 | const macosOctocatStreaking = nativeImage.createFromPath(
69 | join(appPath, 'src/icons/macos/octocat/streaking@2x.png'),
70 | );
71 | macosOctocatStreaking.setTemplateImage(true);
72 |
73 | // Windows Flame
74 | const windowsFlameContributed = nativeImage.createFromPath(
75 | join(appPath, 'src/icons/windows/flame/contributed.ico'),
76 | );
77 | const windowsFlameError = nativeImage.createFromPath(
78 | join(appPath, 'src/icons/windows/flame/error.ico'),
79 | );
80 | const windowsFlamePending = nativeImage.createFromPath(
81 | join(appPath, 'src/icons/windows/flame/pending.ico'),
82 | );
83 | const windowsFlameStreaking = nativeImage.createFromPath(
84 | join(appPath, 'src/icons/windows/flame/streaking.ico'),
85 | );
86 |
87 | // Windows Tile
88 | const windowsTileContributed = nativeImage.createFromPath(
89 | join(appPath, 'src/icons/windows/tile/contributed.ico'),
90 | );
91 | const windowsTileError = nativeImage.createFromPath(
92 | join(appPath, 'src/icons/windows/tile/error.ico'),
93 | );
94 | const windowsTilePending = nativeImage.createFromPath(
95 | join(appPath, 'src/icons/windows/tile/pending.ico'),
96 | );
97 | const windowsTileStreaking = nativeImage.createFromPath(
98 | join(appPath, 'src/icons/windows/tile/streaking.ico'),
99 | );
100 |
101 | // Windows Octocat
102 | const windowsOctocatContributed = nativeImage.createFromPath(
103 | join(appPath, 'src/icons/windows/octocat/contributed.ico'),
104 | );
105 | const windowsOctocatError = nativeImage.createFromPath(
106 | join(appPath, 'src/icons/windows/octocat/error.ico'),
107 | );
108 | const windowsOctocatPending = nativeImage.createFromPath(
109 | join(appPath, 'src/icons/windows/octocat/pending.ico'),
110 | );
111 | const windowsOctocatStreaking = nativeImage.createFromPath(
112 | join(appPath, 'src/icons/windows/octocat/streaking.ico'),
113 | );
114 |
115 | // Linux Flame
116 | const linuxFlameContributed = nativeImage.createFromPath(
117 | join(appPath, 'src/icons/linux/flame/contributed.png'),
118 | );
119 | const linuxFlameError = nativeImage.createFromPath(
120 | join(appPath, 'src/icons/linux/flame/error.png'),
121 | );
122 | const linuxFlamePending = nativeImage.createFromPath(
123 | join(appPath, 'src/icons/linux/flame/pending.png'),
124 | );
125 | const linuxFlameStreaking = nativeImage.createFromPath(
126 | join(appPath, 'src/icons/linux/flame/streaking.png'),
127 | );
128 |
129 | // Linux Tile
130 | const linuxTileContributed = nativeImage.createFromPath(
131 | join(appPath, 'src/icons/linux/tile/contributed.png'),
132 | );
133 | const linuxTileError = nativeImage.createFromPath(
134 | join(appPath, 'src/icons/linux/tile/error.png'),
135 | );
136 | const linuxTilePending = nativeImage.createFromPath(
137 | join(appPath, 'src/icons/linux/tile/pending.png'),
138 | );
139 | const linuxTileStreaking = nativeImage.createFromPath(
140 | join(appPath, 'src/icons/linux/tile/streaking.png'),
141 | );
142 |
143 | // Linux Octocat
144 | const linuxOctocatContributed = nativeImage.createFromPath(
145 | join(appPath, 'src/icons/linux/octocat/contributed.png'),
146 | );
147 | const linuxOctocatError = nativeImage.createFromPath(
148 | join(appPath, 'src/icons/linux/octocat/error.png'),
149 | );
150 | const linuxOctocatPending = nativeImage.createFromPath(
151 | join(appPath, 'src/icons/linux/octocat/pending.png'),
152 | );
153 | const linuxOctocatStreaking = nativeImage.createFromPath(
154 | join(appPath, 'src/icons/linux/octocat/streaking.png'),
155 | );
156 |
157 | switch (process.platform) {
158 | case 'darwin':
159 | icons = {
160 | flame: {
161 | contributed: macosFlameContributed,
162 | error: macosFlameError,
163 | pending: macosFlamePending,
164 | streaking: macosFlameStreaking,
165 | },
166 | tile: {
167 | contributed: macosTileContributed,
168 | error: macosTileError,
169 | pending: macosTilePending,
170 | streaking: macosTileStreaking,
171 | },
172 | octocat: {
173 | contributed: macosOctocatContributed,
174 | error: macosOctocatError,
175 | pending: macosOctocatPending,
176 | streaking: macosOctocatStreaking,
177 | },
178 | };
179 | break;
180 | case 'win32':
181 | icons = {
182 | flame: {
183 | contributed: windowsFlameContributed,
184 | error: windowsFlameError,
185 | pending: windowsFlamePending,
186 | streaking: windowsFlameStreaking,
187 | },
188 | tile: {
189 | contributed: windowsTileContributed,
190 | error: windowsTileError,
191 | pending: windowsTilePending,
192 | streaking: windowsTileStreaking,
193 | },
194 | octocat: {
195 | contributed: windowsOctocatContributed,
196 | error: windowsOctocatError,
197 | pending: windowsOctocatPending,
198 | streaking: windowsOctocatStreaking,
199 | },
200 | };
201 | break;
202 | case 'linux':
203 | icons = {
204 | flame: {
205 | contributed: linuxFlameContributed,
206 | error: linuxFlameError,
207 | pending: linuxFlamePending,
208 | streaking: linuxFlameStreaking,
209 | },
210 | tile: {
211 | contributed: linuxTileContributed,
212 | error: linuxTileError,
213 | pending: linuxTilePending,
214 | streaking: linuxTileStreaking,
215 | },
216 | octocat: {
217 | contributed: linuxOctocatContributed,
218 | error: linuxOctocatError,
219 | pending: linuxOctocatPending,
220 | streaking: linuxOctocatStreaking,
221 | },
222 | };
223 | break;
224 | default: {
225 | throw new Error(
226 | `Platform "${process.platform}" is not currently supported!`,
227 | );
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/icons/linux/flame/contributed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/flame/contributed.png
--------------------------------------------------------------------------------
/src/icons/linux/flame/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/flame/error.png
--------------------------------------------------------------------------------
/src/icons/linux/flame/pending.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/flame/pending.png
--------------------------------------------------------------------------------
/src/icons/linux/flame/streaking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/flame/streaking.png
--------------------------------------------------------------------------------
/src/icons/linux/octocat/contributed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/octocat/contributed.png
--------------------------------------------------------------------------------
/src/icons/linux/octocat/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/octocat/error.png
--------------------------------------------------------------------------------
/src/icons/linux/octocat/pending.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/octocat/pending.png
--------------------------------------------------------------------------------
/src/icons/linux/octocat/streaking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/octocat/streaking.png
--------------------------------------------------------------------------------
/src/icons/linux/tile/contributed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/tile/contributed.png
--------------------------------------------------------------------------------
/src/icons/linux/tile/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/tile/error.png
--------------------------------------------------------------------------------
/src/icons/linux/tile/pending.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/tile/pending.png
--------------------------------------------------------------------------------
/src/icons/linux/tile/streaking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/linux/tile/streaking.png
--------------------------------------------------------------------------------
/src/icons/macos/flame/contributed@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/flame/contributed@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/flame/error@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/flame/error@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/flame/pending@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/flame/pending@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/flame/streaking@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/flame/streaking@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/octocat/contributed@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/octocat/contributed@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/octocat/error@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/octocat/error@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/octocat/pending@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/octocat/pending@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/octocat/streaking@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/octocat/streaking@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/tile/contributed@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/tile/contributed@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/tile/error@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/tile/error@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/tile/pending@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/tile/pending@2x.png
--------------------------------------------------------------------------------
/src/icons/macos/tile/streaking@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/macos/tile/streaking@2x.png
--------------------------------------------------------------------------------
/src/icons/windows/flame/contributed.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/flame/contributed.ico
--------------------------------------------------------------------------------
/src/icons/windows/flame/error.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/flame/error.ico
--------------------------------------------------------------------------------
/src/icons/windows/flame/pending.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/flame/pending.ico
--------------------------------------------------------------------------------
/src/icons/windows/flame/streaking.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/flame/streaking.ico
--------------------------------------------------------------------------------
/src/icons/windows/octocat/contributed.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/octocat/contributed.ico
--------------------------------------------------------------------------------
/src/icons/windows/octocat/error.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/octocat/error.ico
--------------------------------------------------------------------------------
/src/icons/windows/octocat/pending.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/octocat/pending.ico
--------------------------------------------------------------------------------
/src/icons/windows/octocat/streaking.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/octocat/streaking.ico
--------------------------------------------------------------------------------
/src/icons/windows/tile/contributed.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/tile/contributed.ico
--------------------------------------------------------------------------------
/src/icons/windows/tile/error.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/tile/error.ico
--------------------------------------------------------------------------------
/src/icons/windows/tile/pending.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/tile/pending.ico
--------------------------------------------------------------------------------
/src/icons/windows/tile/streaking.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamieweavis/streaker/90568ab1f3249aef18b9de7dfa4a1aabb9c29e42/src/icons/windows/tile/streaking.ico
--------------------------------------------------------------------------------
/src/main/logger.ts:
--------------------------------------------------------------------------------
1 | import log from 'electron-log';
2 |
3 | export const logger = log;
4 |
--------------------------------------------------------------------------------
/src/main/main.ts:
--------------------------------------------------------------------------------
1 | import AutoLaunch from 'auto-launch';
2 | import { type GitHubStats, fetchGitHubStats } from 'contribution';
3 | import { CronJob, CronTime } from 'cron';
4 | import type { NativeImage } from 'electron';
5 | import { BrowserWindow, Notification, Tray, app, ipcMain } from 'electron';
6 | import { UpdateSourceType, updateElectronApp } from 'update-electron-app';
7 |
8 | import { icons } from '../icons';
9 | import { logger } from './logger';
10 | import MenuBuilder from './menu';
11 | import { store } from './store';
12 | import { createTrayMenu } from './tray-menu';
13 |
14 | declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
15 | declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
16 |
17 | export interface Preferences {
18 | username: string;
19 | launchAtLogin: boolean;
20 | reminderEnabled: boolean;
21 | reminderTime: string;
22 | iconTheme: string;
23 | }
24 |
25 | const isDev = process.env.NODE_ENV === 'development';
26 |
27 | const bootstrap = (): void => {
28 | const { username, iconTheme, reminderTime, reminderEnabled } = store.store;
29 |
30 | let preferencesWindow: BrowserWindow | null = null;
31 | let lastFetchedStats: GitHubStats | undefined;
32 |
33 | const tray = new Tray(icons[iconTheme].pending);
34 |
35 | const createPreferencesWindow = () => {
36 | preferencesWindow = new BrowserWindow({
37 | width: 310,
38 | height: 485,
39 | show: false,
40 | resizable: false,
41 | maximizable: false,
42 | minimizable: false,
43 | fullscreenable: false,
44 | frame: false,
45 | webPreferences: {
46 | preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
47 | additionalArguments: Object.entries(store.store).map(
48 | ([key, value]) => `--${key}=${value}`,
49 | ),
50 | },
51 | });
52 |
53 | preferencesWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
54 |
55 | const menuBuilder = new MenuBuilder(preferencesWindow);
56 | menuBuilder.buildMenu();
57 |
58 | preferencesWindow.on('ready-to-show', () => {
59 | if (preferencesWindow) {
60 | preferencesWindow.show();
61 | if (isDev) preferencesWindow.webContents.openDevTools();
62 | }
63 | if (process.platform === 'darwin') app.dock.show();
64 | });
65 |
66 | preferencesWindow.on('closed', () => {
67 | preferencesWindow = null;
68 | if (process.platform === 'darwin') app.dock.hide();
69 | });
70 | };
71 |
72 | const createTray = ({
73 | username,
74 | gitHubStats,
75 | icon,
76 | }: {
77 | username?: string;
78 | gitHubStats?: GitHubStats;
79 | icon?: NativeImage;
80 | }) => {
81 | const trayMenu = createTrayMenu({
82 | username,
83 | gitHubStats,
84 | createPreferencesWindow: createPreferencesWindow,
85 | requestContributionData: fetchContributionStats,
86 | isPreferencesWindowOpen: () => !!preferencesWindow,
87 | focusPreferencesWindow: () => {
88 | if (!preferencesWindow) return;
89 | if (preferencesWindow.isMinimized()) preferencesWindow.restore();
90 | preferencesWindow.focus();
91 | },
92 | });
93 | tray.setContextMenu(trayMenu);
94 | if (icon) tray.setImage(icon);
95 | };
96 |
97 | const fetchContributionStats = async () => {
98 | const username = store.get('username');
99 | const iconTheme = store.get('iconTheme');
100 |
101 | if (!username) {
102 | return createTray({ icon: icons[iconTheme].error });
103 | }
104 |
105 | let gitHubStats: GitHubStats | undefined;
106 | let icon = icons[iconTheme].pending;
107 |
108 | try {
109 | gitHubStats = await fetchGitHubStats(username);
110 | logger.info('Fetched contribution stats', gitHubStats);
111 |
112 | lastFetchedStats = gitHubStats;
113 |
114 | if (gitHubStats.currentStreak > 0) {
115 | icon = icons[iconTheme].contributed;
116 | }
117 | if (gitHubStats.currentStreak === gitHubStats.bestStreak) {
118 | icon = icons[iconTheme].streaking;
119 | }
120 | } catch (err) {
121 | icon = icons[iconTheme].error;
122 | }
123 |
124 | createTray({ username, gitHubStats, icon });
125 | };
126 |
127 | const savePreferences = (preferences: Preferences) => {
128 | logger.info('Saving preferences:', preferences);
129 |
130 | // Update auto launch
131 | try {
132 | if (preferences.launchAtLogin !== store.get('launchAtLogin')) {
133 | if (preferences.launchAtLogin) {
134 | autoLaunch.enable();
135 | logger.info('Auto launch enabled');
136 | } else {
137 | autoLaunch.disable();
138 | logger.info('Auto launch disabled');
139 | }
140 | }
141 | } catch (error) {
142 | logger.error('Error enabling auto launch', error);
143 | }
144 |
145 | // Update reminder notification
146 | try {
147 | if (
148 | preferences.reminderEnabled !== store.get('reminderEnabled') ||
149 | preferences.reminderTime !== store.get('reminderTime')
150 | ) {
151 | if (preferences.reminderEnabled) {
152 | const [hours, minutes] = preferences.reminderTime.split(':');
153 | const cronTime = new CronTime(`${minutes} ${hours} * * *`);
154 | reminderNotification.setTime(cronTime);
155 | reminderNotification.start();
156 | logger.info(
157 | 'Reminder notification enabled:',
158 | preferences.reminderTime,
159 | );
160 | } else {
161 | reminderNotification.stop();
162 | logger.info('Reminder notification disabled');
163 | }
164 | }
165 | } catch (error) {
166 | logger.error('Error enabling reminder notification', error);
167 | }
168 |
169 | // Save preferences
170 | store.set(preferences);
171 | logger.info('Preferences saved');
172 | };
173 |
174 | const showReminderNotification = () => {
175 | logger.info('Triggering reminder notification');
176 |
177 | try {
178 | if (!Notification.isSupported()) {
179 | logger.error('Notification not supported');
180 | return;
181 | }
182 | if (!lastFetchedStats.isStreakAtRisk) {
183 | logger.info('Streak not at risk, skipping reminder notification');
184 | return;
185 | }
186 | new Notification({
187 | title: `🔥 Don't lose your streak!`,
188 | body: `Contribute today to continue your ${lastFetchedStats.previousStreak} day streak on GitHub`,
189 | }).show();
190 | } catch (error) {
191 | logger.error('Error showing reminder notification', error);
192 | }
193 | };
194 |
195 | app.on('window-all-closed', () => {});
196 | if (process.platform === 'darwin') app.dock.hide();
197 |
198 | app.on('activate', () => {
199 | if (!preferencesWindow) createPreferencesWindow();
200 | });
201 |
202 | // Fetch data on app start and every minute
203 | fetchContributionStats();
204 | new CronJob('* * * * *', fetchContributionStats, null, true);
205 |
206 | // Handle saving preferences
207 | ipcMain.on('savePreferences', (_, preferences: Preferences) => {
208 | savePreferences(preferences);
209 | fetchContributionStats();
210 | });
211 |
212 | // Set up reminder notification
213 | const [hours, minutes] = reminderTime.split(':');
214 | const reminderNotification = new CronJob(
215 | `${minutes} ${hours} * * *`,
216 | showReminderNotification,
217 | );
218 | if (reminderEnabled) reminderNotification.start();
219 |
220 | // Set up auto launch
221 | const autoLaunch = new AutoLaunch({
222 | name: 'Streaker',
223 | isHidden: true,
224 | });
225 |
226 | // Set up auto updater
227 | updateElectronApp({
228 | updateSource: {
229 | type: UpdateSourceType.ElectronPublicUpdateService,
230 | repo: 'jamieweavis/streaker',
231 | },
232 | updateInterval: '1 hour',
233 | logger: logger,
234 | });
235 |
236 | // Open preferences window automatically if username is not set (most likely fresh install)
237 | if (!username || isDev) createPreferencesWindow();
238 | };
239 |
240 | app
241 | .whenReady()
242 | .then(bootstrap)
243 | .catch((error) => {
244 | logger.error('An error occurred', error);
245 | });
246 |
--------------------------------------------------------------------------------
/src/main/menu.ts:
--------------------------------------------------------------------------------
1 | import { Menu, app } from 'electron';
2 | import type { BrowserWindow, MenuItemConstructorOptions } from 'electron';
3 |
4 | interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
5 | selector?: string;
6 | submenu?: DarwinMenuItemConstructorOptions[] | Menu;
7 | }
8 |
9 | export default class MenuBuilder {
10 | mainWindow: BrowserWindow;
11 |
12 | constructor(mainWindow: BrowserWindow) {
13 | this.mainWindow = mainWindow;
14 | }
15 |
16 | buildMenu(): Menu {
17 | if (
18 | process.env.NODE_ENV === 'development' ||
19 | process.env.DEBUG_PROD === 'true'
20 | ) {
21 | this.setupDevelopmentEnvironment();
22 | }
23 |
24 | const template =
25 | process.platform === 'darwin'
26 | ? this.buildDarwinTemplate()
27 | : this.buildDefaultTemplate();
28 |
29 | const menu = Menu.buildFromTemplate(template);
30 | Menu.setApplicationMenu(menu);
31 |
32 | return menu;
33 | }
34 |
35 | setupDevelopmentEnvironment(): void {
36 | this.mainWindow.webContents.on('context-menu', (_, props) => {
37 | const { x, y } = props;
38 |
39 | Menu.buildFromTemplate([
40 | {
41 | label: 'Inspect element',
42 | click: () => {
43 | this.mainWindow.webContents.inspectElement(x, y);
44 | },
45 | },
46 | ]).popup({ window: this.mainWindow });
47 | });
48 | }
49 |
50 | buildDarwinTemplate(): MenuItemConstructorOptions[] {
51 | const subMenuAbout: DarwinMenuItemConstructorOptions = {
52 | label: 'Streaker',
53 | submenu: [
54 | {
55 | label: 'About Streaker',
56 | selector: 'orderFrontStandardAboutPanel:',
57 | },
58 | { type: 'separator' },
59 | {
60 | label: 'Quit',
61 | accelerator: 'Command+Q',
62 | click: () => {
63 | app.quit();
64 | },
65 | },
66 | ],
67 | };
68 |
69 | const subMenuEdit: DarwinMenuItemConstructorOptions = {
70 | label: 'Edit',
71 | submenu: [
72 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
73 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
74 | { type: 'separator' },
75 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
76 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
77 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
78 | {
79 | label: 'Select All',
80 | accelerator: 'Command+A',
81 | selector: 'selectAll:',
82 | },
83 | ],
84 | };
85 |
86 | return [subMenuAbout, subMenuEdit];
87 | }
88 |
89 | buildDefaultTemplate() {
90 | const templateDefault = [
91 | {
92 | label: '&File',
93 | submenu: [
94 | {
95 | label: '&Close',
96 | accelerator: 'Ctrl+W',
97 | click: () => {
98 | this.mainWindow.close();
99 | },
100 | },
101 | ],
102 | },
103 | ];
104 |
105 | return templateDefault;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/store.ts:
--------------------------------------------------------------------------------
1 | import Store from 'electron-store';
2 |
3 | export const store = new Store({
4 | defaults: {
5 | username: '',
6 | launchAtLogin: false,
7 | reminderEnabled: false,
8 | reminderTime: '20:00',
9 | iconTheme: 'flame',
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/src/main/tray-menu.ts:
--------------------------------------------------------------------------------
1 | import type { GitHubStats } from 'contribution';
2 | import { Menu, app, shell } from 'electron';
3 |
4 | import pjson from '../../package.json';
5 |
6 | export interface CreateTrayMenuOptions {
7 | username?: string;
8 | gitHubStats?: GitHubStats;
9 | createPreferencesWindow: () => void;
10 | requestContributionData: () => void;
11 | isPreferencesWindowOpen: () => boolean;
12 | focusPreferencesWindow: () => void;
13 | }
14 |
15 | export const createTrayMenu = ({
16 | username,
17 | gitHubStats,
18 | createPreferencesWindow,
19 | requestContributionData,
20 | isPreferencesWindowOpen,
21 | focusPreferencesWindow,
22 | }: CreateTrayMenuOptions): Menu => {
23 | const createOrFocusPreferencesWindow = () => {
24 | if (!isPreferencesWindowOpen()) return createPreferencesWindow();
25 | focusPreferencesWindow();
26 | };
27 |
28 | return Menu.buildFromTemplate([
29 | {
30 | label: username || 'Set GitHub username...',
31 | enabled: !username,
32 | click: createOrFocusPreferencesWindow,
33 | },
34 | { type: 'separator' },
35 | { label: 'Streak:', enabled: false },
36 | {
37 | label: ` Best:\t\t${gitHubStats.bestStreak || 0}`,
38 | enabled: false,
39 | },
40 | {
41 | label: ` Current:\t${gitHubStats.currentStreak || 0}`,
42 | enabled: false,
43 | },
44 | {
45 | label: ` Previous:\t${gitHubStats.previousStreak || 0}${gitHubStats.isStreakAtRisk ? '\t(at risk!)' : ''}`,
46 | enabled: false,
47 | },
48 | { label: 'Contributions:', enabled: false },
49 | {
50 | label: ` Most:\t\t${gitHubStats.mostContributions || 0}`,
51 | enabled: false,
52 | },
53 | {
54 | label: ` Today:\t\t${gitHubStats.todaysContributions || 0}`,
55 | enabled: false,
56 | },
57 | {
58 | label: ` Total:\t\t${gitHubStats.totalContributions || 0}`,
59 | enabled: false,
60 | },
61 | { type: 'separator' },
62 | {
63 | label: 'Reload',
64 | enabled: !!username,
65 | accelerator: 'CmdOrCtrl+R',
66 | click: requestContributionData,
67 | },
68 | {
69 | label: 'Open GitHub Profile...',
70 | enabled: !!username,
71 | accelerator: 'CmdOrCtrl+O',
72 | click: () => shell.openExternal(`https://github.com/${username}`),
73 | },
74 | { type: 'separator' },
75 | {
76 | label: 'Preferences...',
77 | accelerator: 'CmdOrCtrl+,',
78 | click: createOrFocusPreferencesWindow,
79 | },
80 | {
81 | label: 'About Streaker...',
82 | click: () => shell.openExternal(pjson.homepage),
83 | },
84 | {
85 | label: 'Feedback && Support...',
86 | click: () => shell.openExternal(pjson.bugs.url),
87 | },
88 | { type: 'separator' },
89 | {
90 | label: 'Quit Streaker',
91 | accelerator: 'CmdOrCtrl+Q',
92 | click: () => app.quit(),
93 | },
94 | ]);
95 | };
96 |
--------------------------------------------------------------------------------
/src/renderer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |