├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── img
├── ams.png
├── dose-icns.icns
├── dose-logo.png
└── dose.png
└── src
├── ams_filter.zsh
├── ams_pro.zsh
├── dose_filter.zsh
└── dose_pro.zsh
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: vanstrouble
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2025 Pedro Vázquez
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Amphetamine Dose | Alfred Workflow
2 |
3 | Control the [Amphetamine app](https://apps.apple.com/us/app/amphetamine/id937984704?mt=12) straight from Alfred — start or stop sessions and set how long your Mac should stay awake, all without lifting your hands from the keyboard.
4 |
5 | ## Download
6 |
7 | - Available on the Alfred Gallery. Get it [here](https://alfred.app/workflows/vanstrouble/amphetamine-dose/).
8 | - You can also download it from GitHub [here](https://github.com/vanstrouble/dose-alfred-workflow/releases/latest).
9 |
10 | **Not a fan of fancy extras? Go native with _Caffeine Dose_ — it runs on `caffeinate`, no sugar, no cream. Just pure workflow. [Try it here](https://github.com/vanstrouble/caffeine-dose-alfred-workflow.git).**
11 |
12 | ## Usage
13 |
14 | ### keep your Mac awake (dose)
15 |
16 |
17 |
18 | Use the `dose` keyword to toggle Amphetamine on or off, preventing macOS from sleeping.
19 |
20 | - **Keyword:** `dose`
21 |
22 | Hold the **Command (⌘)** key while using the `dose` command, the session will allow the display to sleep.
23 |
24 | ### One command for everything (ams)
25 |
26 |
27 |
28 | The `ams` command allows you to set Amphetamine to keep your Mac awake for a specific duration or until a specific time. It supports natural input formats for minutes, hours, and specific times, making it flexible and easy to use.
29 |
30 | - **Keyword:** `ams [duration or time]`
31 |
32 | Hold the **Command (⌘)** key while using the `ams` command, the session will allow the display to sleep.
33 |
34 | #### Examples:
35 |
36 | | Command | Description |
37 | |------------|------------------------------------------------|
38 | | `ams i` | Keeps your Mac awake indefinitely. |
39 | | `ams 15` | Keeps your Mac awake for 15 minutes. |
40 | | `ams 2h` | Keeps your Mac awake for 2 hours. |
41 | | `ams 1 30` | Keeps your Mac awake for 1 hour and 30 minutes.|
42 | | `ams 9:30` | Keeps your Mac awake until the next 9:30. |
43 | | `ams 8am` | Keeps your Mac awake until 8:00 AM. |
44 | | `ams 11:40pm` | Keeps your Mac awake until 11:40 PM. |
45 |
46 | The `ams` command supports both 12-hour (AM/PM) and 24-hour time formats.
47 |
48 | ### Customization
49 |
50 | Both `dose` and `ams` commands can be customized in the workflow settings. You can modify their keywords or behavior to better suit your needs.
51 |
--------------------------------------------------------------------------------
/img/ams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vanstrouble/dose-alfred-workflow/aa96b28b72ea16f655e3a6d0d387ee1f9a5e2b56/img/ams.png
--------------------------------------------------------------------------------
/img/dose-icns.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vanstrouble/dose-alfred-workflow/aa96b28b72ea16f655e3a6d0d387ee1f9a5e2b56/img/dose-icns.icns
--------------------------------------------------------------------------------
/img/dose-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vanstrouble/dose-alfred-workflow/aa96b28b72ea16f655e3a6d0d387ee1f9a5e2b56/img/dose-logo.png
--------------------------------------------------------------------------------
/img/dose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vanstrouble/dose-alfred-workflow/aa96b28b72ea16f655e3a6d0d387ee1f9a5e2b56/img/dose.png
--------------------------------------------------------------------------------
/src/ams_filter.zsh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh --no-rcs
2 |
3 | # Function to calculate the end time based on the given minutes
4 | calculate_end_time() {
5 | local minutes=$1
6 |
7 | # Check Alfred variable for time format preference
8 | # 'a' is 12-hour format, 'b' is 24-hour format
9 | if [[ "${alfred_time_format:-a}" == "a" ]]; then
10 | # 12-hour format with AM/PM including seconds
11 | date -v+"$minutes"M +"%l:%M:%S %p" | sed 's/^ //'
12 | else
13 | # 24-hour format including seconds
14 | date -v+"$minutes"M +"%H:%M:%S"
15 | fi
16 | }
17 |
18 | # Function to get the nearest future time based on input hour and minute
19 | get_nearest_future_time() {
20 | local hour=$1
21 | local minute=$2
22 | local current_hour=$3
23 | local current_minute=$4
24 |
25 | # Calculate current time in minutes since midnight (once instead of twice)
26 | local current_total=$(( current_hour * 60 + current_minute ))
27 |
28 | # Special handling for hour 12 and conversion to AM/PM using shorter syntax
29 | local am_hour=$hour
30 | local pm_hour=$hour
31 | [[ $hour -eq 12 ]] && am_hour=0 # 12 AM is actually 0 in 24-hour format
32 | [[ $hour -lt 12 ]] && pm_hour=$(( hour + 12 ))
33 |
34 | # Calculate minutes for AM and PM interpretations
35 | local am_total=$(( am_hour * 60 + minute ))
36 | local pm_total=$(( pm_hour * 60 + minute ))
37 |
38 | # Calculate differences once
39 | local am_diff=$(( am_total - current_total ))
40 | local pm_diff=$(( pm_total - current_total ))
41 |
42 | # Use the same logic but with pre-calculated differences
43 | if [[ $am_diff -lt 0 && $pm_diff -gt 0 ]]; then
44 | echo $pm_diff
45 | elif [[ $am_diff -gt 0 ]]; then
46 | echo $am_diff
47 | else
48 | echo $(( am_diff + 1440 ))
49 | fi
50 | }
51 |
52 | # Helper function to format hours with leading zero
53 | format_hour() {
54 | local hour=$1
55 | # Ensure hour is a number without leading zeros
56 | hour=${hour#0}
57 | [[ -z "$hour" ]] && hour=0
58 | [[ "$hour" -lt 10 ]] && echo "0$hour" || echo "$hour"
59 | }
60 |
61 | # Helper function to format minutes with leading zero
62 | format_minute() {
63 | local minute=$1
64 | # Ensure minute is a number without leading zeros
65 | minute=${minute#0}
66 | [[ -z "$minute" ]] && minute=0
67 | [[ "$minute" -lt 10 ]] && echo "0$minute" || echo "$minute"
68 | }
69 |
70 | # Helper function to convert AM/PM hour to 24-hour format
71 | convert_to_24h_format() {
72 | local hour=$1
73 | local ampm=$2
74 |
75 | # Trim leading zeros
76 | hour=${hour#0}
77 | [[ -z "$hour" ]] && hour=0
78 |
79 | if [[ "$ampm" =~ [pP] && "$hour" -lt 12 ]]; then
80 | echo $(( hour + 12 ))
81 | elif [[ "$ampm" =~ [aA] && "$hour" -eq 12 ]]; then
82 | echo 0
83 | else
84 | echo $hour
85 | fi
86 | }
87 |
88 | # Helper function to calculate future time from minutes
89 | calculate_future_time() {
90 | local total_minutes=$1
91 | local current_hour=$2
92 | local current_minute=$3
93 |
94 | local future_hour=$(( (total_minutes + current_hour * 60 + current_minute) / 60 % 24 ))
95 | local future_minute=$(( (total_minutes + current_hour * 60 + current_minute) % 60 ))
96 |
97 | # Format with leading zeros (after removing any existing leading zeros)
98 | future_hour=$(format_hour "$future_hour")
99 | future_minute=$(format_minute "$future_minute")
100 |
101 | echo "TIME:$future_hour:$future_minute"
102 | }
103 |
104 | # Function to parse the input and calculate the total minutes
105 | parse_input() {
106 | local input=(${(@s/ /)1}) # Split the input into parts
107 | local current_hour=$(date +"%H")
108 | local current_minute=$(date +"%M")
109 |
110 | # Early return for invalid input when empty
111 | [[ -z "${input[1]}" ]] && echo "0" && return
112 |
113 | # Handle single input cases with early returns
114 | if [[ "${#input[@]}" -eq 1 ]]; then
115 | # Special value for indefinite mode
116 | [[ "${input[1]}" == "i" ]] && echo "indefinite" && return
117 |
118 | # Format: 2h (hours)
119 | if [[ "${input[1]}" =~ ^[0-9]+h$ ]]; then
120 | echo $(( ${input[1]%h} * 60 ))
121 | return
122 | fi
123 |
124 | # Direct number input (minutes)
125 | if [[ "${input[1]}" =~ ^[0-9]+$ ]]; then
126 | echo "${input[1]}"
127 | return
128 | fi
129 |
130 | # Format: 8 or 8: (hour only)
131 | if [[ "${input[1]}" =~ ^([0-9]{1,2}):?$ ]]; then
132 | local hour=${match[1]}
133 | local minute=0
134 |
135 | # Parameter expansion is more efficient than sed
136 | hour=${hour#0}
137 |
138 | # Check if the input has a colon at the end
139 | if [[ "${input[1]}" =~ :$ ]]; then
140 | # If it has a colon, calculate specific time
141 | local total_minutes=$(get_nearest_future_time "$hour" "$minute" "$current_hour" "$current_minute")
142 |
143 | # Use helper function to calculate future time
144 | local future_time=$(calculate_future_time "$total_minutes" "$current_hour" "$current_minute")
145 | # For hour-only format with colon, we want to force minutes to 00
146 | echo "${future_time%:*}:00"
147 | else
148 | # No colon, return minutes
149 | local total_minutes=$(get_nearest_future_time "$hour" "$minute" "$current_hour" "$current_minute")
150 | echo "$total_minutes"
151 | fi
152 | return
153 | fi
154 |
155 | # Format: 8a, 8am, 8p, 8pm
156 | if [[ "${input[1]}" =~ ^([0-9]{1,2})([aApP])?(m)?$ ]]; then
157 | local hour=${match[1]}
158 | local ampm=${match[2]:-""}
159 | local minute=0
160 |
161 | # With AM/PM indicator
162 | if [[ -n "$ampm" ]]; then
163 | # Convert to 24-hour format using helper function
164 | hour=$(convert_to_24h_format "$hour" "$ampm")
165 |
166 | # Format hour with leading zero
167 | hour=$(format_hour "$hour")
168 | echo "TIME:$hour:00"
169 | else
170 | # Without AM/PM, use nearest future time
171 | hour=${hour#0}
172 | echo $(get_nearest_future_time "$hour" "$minute" "$current_hour" "$current_minute")
173 | fi
174 | return
175 | fi
176 |
177 | # Format: 8:30, 8:30a, 8:30am, 8:30p, 8:30pm
178 | if [[ "${input[1]}" =~ ^([0-9]{1,2}):([0-9]{1,2})([aApP])?([mM])?$ ]]; then
179 | local hour=${match[1]}
180 | local minute=${match[2]}
181 | local ampm=${match[3]:-""}
182 |
183 | # With AM/PM indicator
184 | if [[ -n "$ampm" ]]; then
185 | # Convert to 24-hour format using helper function
186 | hour=$(convert_to_24h_format "$hour" "$ampm")
187 |
188 | # Format output with leading zeros
189 | hour=$(format_hour "$hour")
190 | minute=$(format_minute "$minute")
191 | echo "TIME:$hour:$minute"
192 | else
193 | # Without explicit AM/PM, calculate future time
194 | hour=${hour#0}
195 | local total_minutes=$(get_nearest_future_time "$hour" "$minute" "$current_hour" "$current_minute")
196 |
197 | # Use helper function to calculate and format future time
198 | echo $(calculate_future_time "$total_minutes" "$current_hour" "$current_minute")
199 | fi
200 | return
201 | fi
202 |
203 | # If we get here, it's an invalid single input
204 | echo "0"
205 | return
206 | fi
207 |
208 | # Handle two-part input (hours and minutes)
209 | if [[ "${#input[@]}" -eq 2 ]]; then
210 | if [[ "${input[1]}" =~ ^[0-9]+$ && "${input[2]}" =~ ^[0-9]+$ ]]; then
211 | echo $(( input[1] * 60 + input[2] ))
212 | return
213 | fi
214 |
215 | # Invalid two-part input
216 | echo "0"
217 | return
218 | fi
219 |
220 | # Default case: invalid input
221 | echo "0"
222 | }
223 |
224 | # Function to format the duration in hours and minutes
225 | format_duration() {
226 | local total_minutes=$1
227 | local hours=$(( total_minutes / 60 ))
228 | local minutes=$(( total_minutes % 60 ))
229 |
230 | if [[ "$hours" -gt 0 && "$minutes" -gt 0 ]]; then
231 | echo "$hours hour(s) $minutes minute(s)"
232 | elif [[ "$hours" -gt 0 ]]; then
233 | echo "$hours hour(s)"
234 | else
235 | echo "$minutes minute(s)"
236 | fi
237 | }
238 |
239 | # Function to generate Alfred JSON output
240 | generate_output() {
241 | local input_result=$1
242 |
243 | # Check for invalid input first (fastest check)
244 | if [[ "$input_result" == "0" ]]; then
245 | echo '{"items":[{"title":"Invalid input","subtitle":"Please provide a valid time format","arg":"0","icon":{"path":"icon.png"}}]}'
246 | return
247 | fi
248 |
249 | # Check for indefinite mode (no rerun needed)
250 | if [[ "$input_result" == "indefinite" ]]; then
251 | echo '{"items":[{"title":"Active indefinitely","subtitle":"Keep your Mac awake until manually disabled","arg":"indefinite","icon":{"path":"icon.png"}}]}'
252 | return
253 | fi
254 |
255 | # Check for target time format
256 | if [[ "$input_result" == TIME:* ]]; then
257 | local target_time=${input_result#TIME:}
258 | local hour=${target_time%:*}
259 | local minute=${target_time#*:}
260 |
261 | # To display the time in a user-friendly format
262 | local display_time=$(date -j -f "%H:%M" "$target_time" "+%l:%M %p" 2>/dev/null | sed 's/^ //')
263 | [[ $? -ne 0 ]] && display_time="$target_time"
264 |
265 | echo '{"items":[{"title":"Active until '"$display_time"'","subtitle":"Keep awake until specified time","arg":"'"$input_result"'","icon":{"path":"icon.png"}}]}'
266 | return
267 | fi
268 |
269 | # Finally, handle duration in minutes (most common case)
270 | local end_time=$(calculate_end_time "$input_result")
271 | local formatted_duration=$(format_duration "$input_result")
272 | echo '{"rerun":1,"items":[{"title":"Active for '"$formatted_duration"'","subtitle":"Keep awake until around '"$end_time"'","arg":"'"$input_result"'","icon":{"path":"icon.png"}}]}'
273 | }
274 |
275 | # Main function
276 | main() {
277 | local total_minutes=$(parse_input "$1")
278 | generate_output "$total_minutes"
279 | }
280 |
281 | # Execute the main function with the input
282 | main "$1"
283 |
--------------------------------------------------------------------------------
/src/ams_pro.zsh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Function to calculate the end time based on the given minutes
4 | calculate_end_time() {
5 | local minutes=$1
6 |
7 | # Check Alfred variable for time format preference
8 | if [[ "${alfred_time_format:-a}" == "a" ]]; then
9 | # 12-hour format with AM/PM
10 | date -v+"$minutes"M +"%l:%M %p" | sed 's/^ //'
11 | else
12 | # 24-hour format
13 | date -v+"$minutes"M +"%H:%M"
14 | fi
15 | }
16 |
17 | # Function to extract hour and minute from TIME:HH:MM format
18 | parse_time_format() {
19 | local time_str=$1
20 |
21 | # Remove the TIME: prefix
22 | time_str=${time_str#TIME:}
23 |
24 | # Extract hour and minute directly
25 | local hour=${time_str%%:*}
26 | local minute=${time_str#*:}
27 |
28 | # Validate input - purely numeric and in range
29 | if [[ ! "$hour" =~ ^[0-9]+$ || ! "$minute" =~ ^[0-9]+$ || "$hour" -gt 23 || "$minute" -gt 59 ]]; then
30 | echo "Error: Invalid time format: $time_str" >&2
31 | exit 1
32 | fi
33 |
34 | # Return as space-separated values
35 | echo "$hour $minute"
36 | }
37 |
38 | # Function to calculate minutes until target time
39 | calculate_minutes_until_target() {
40 | local hour=$1
41 | local minute=$2
42 |
43 | # Get current time
44 | local current_hour=$(date +"%H")
45 | local current_minute=$(date +"%M")
46 |
47 | # Calculate total minutes
48 | local target_minutes=$(( hour * 60 + minute ))
49 | local current_minutes=$(( current_hour * 60 + current_minute ))
50 | local duration_minutes=$(( target_minutes - current_minutes ))
51 |
52 | # If target time is earlier than current time, add 24 hours
53 | [[ $duration_minutes -le 0 ]] && duration_minutes=$(( duration_minutes + 1440 ))
54 |
55 | echo "$duration_minutes"
56 | }
57 |
58 | # Function to format time for display
59 | format_display_time() {
60 | local hour=$1
61 | local minute=$2
62 | local time_format=${3:-a}
63 |
64 | local display_time
65 |
66 | if [[ "$time_format" == "a" ]]; then
67 | # 12-hour format
68 | if [[ "$hour" -gt 12 ]]; then
69 | display_time="$((hour-12)):${minute} PM"
70 | elif [[ "$hour" -eq 12 ]]; then
71 | display_time="12:${minute} PM"
72 | elif [[ "$hour" -eq 0 ]]; then
73 | display_time="12:${minute} AM"
74 | else
75 | display_time="${hour}:${minute} AM"
76 | fi
77 |
78 | # Ensure minutes have leading zero if needed
79 | [[ ${#minute} -eq 1 ]] && display_time="${display_time/\:$minute/\:0$minute}"
80 | else
81 | # 24-hour format
82 | [[ ${#hour} -eq 1 ]] && hour="0$hour"
83 | [[ ${#minute} -eq 1 ]] && minute="0$minute"
84 | display_time="${hour}:${minute}"
85 | fi
86 |
87 | echo "$display_time"
88 | }
89 |
90 | # Function to print output message based on display sleep setting
91 | output_message() {
92 | local message=$1
93 | local approximate=$2
94 | local allow_display_sleep=$3
95 |
96 | local prefix="Keeping awake"
97 | local suffix
98 |
99 | if [[ "$allow_display_sleep" == "true" ]]; then
100 | suffix=". (Display can sleep)"
101 | else
102 | suffix="."
103 | fi
104 |
105 | if [[ -n "$approximate" && "$approximate" == "true" ]]; then
106 | echo "${prefix} until around ${message}${suffix}"
107 | else
108 | echo "${prefix} until ${message}${suffix}"
109 | fi
110 | }
111 |
112 | # Function to print indefinite output message based on display sleep setting
113 | output_indefinite_message() {
114 | local allow_display_sleep=$1
115 |
116 | if [[ "$allow_display_sleep" == "true" ]]; then
117 | echo "Keeping awake indefinitely. (Display can sleep)"
118 | else
119 | echo "Keeping awake indefinitely."
120 | fi
121 | }
122 |
123 | # Function to start an Amphetamine session with minutes
124 | start_amphetamine_session() {
125 | local total_minutes=$1
126 | local allow_display_sleep=$2
127 |
128 | # Ensure the minutes are a valid number and greater than 0
129 | if [[ ! "$total_minutes" =~ ^[0-9]+$ || "$total_minutes" -eq 0 ]]; then
130 | echo "Error: Invalid duration: $total_minutes minutes" >&2
131 | exit 1
132 | fi
133 |
134 | osascript -e "tell application \"Amphetamine\" to start new session with options {duration:$total_minutes, interval:minutes, displaySleepAllowed:$allow_display_sleep}" || {
135 | echo "Error: Failed to start Amphetamine session." >&2
136 | exit 1
137 | }
138 | }
139 |
140 | # Function to start an indefinite Amphetamine session
141 | start_indefinite_session() {
142 | local allow_display_sleep=$1
143 |
144 | osascript -e "tell application \"Amphetamine\" to start new session with options {displaySleepAllowed:$allow_display_sleep}" || {
145 | echo "Error: Failed to start Amphetamine session." >&2
146 | exit 1
147 | }
148 |
149 | output_indefinite_message "$allow_display_sleep"
150 | }
151 |
152 | # Function to handle target time input
153 | handle_target_time() {
154 | local target_time=$1
155 | local allow_display_sleep=$2
156 |
157 | # Extract hour and minute
158 | read -r hour minute <<< "$(parse_time_format "$target_time")"
159 |
160 | # Calculate minutes until target time
161 | local duration_minutes=$(calculate_minutes_until_target "$hour" "$minute")
162 |
163 | # Start the session
164 | start_amphetamine_session "$duration_minutes" "$allow_display_sleep"
165 |
166 | # Format time for display
167 | local display_time=$(format_display_time "$hour" "$minute" "${alfred_time_format:-a}")
168 |
169 | # Output result message
170 | output_message "$display_time" "false" "$allow_display_sleep"
171 | }
172 |
173 | # Function to handle minute duration input
174 | handle_duration() {
175 | local minutes=$1
176 | local allow_display_sleep=$2
177 |
178 | # Calculate end time for display
179 | local end_time=$(calculate_end_time "$minutes")
180 |
181 | # Start the session
182 | start_amphetamine_session "$minutes" "$allow_display_sleep"
183 |
184 | # Output result message using the common function
185 | output_message "$end_time" "true" "$allow_display_sleep"
186 | }
187 |
188 | # Main function
189 | main() {
190 | # Default value for display_sleep_allow if not set
191 | display_sleep_allow=${display_sleep_allow:-false}
192 |
193 | # Handle different input types from the Filter Script
194 | if [[ "$INPUT" == "0" ]]; then
195 | echo "Error: Invalid input. Please provide a valid duration."
196 | exit 1
197 | elif [[ "$INPUT" == "indefinite" ]]; then
198 | start_indefinite_session "$display_sleep_allow"
199 | elif [[ "$INPUT" == TIME:* ]]; then
200 | handle_target_time "$INPUT" "$display_sleep_allow"
201 | elif [[ "$INPUT" =~ ^[0-9]+$ ]]; then
202 | handle_duration "$INPUT" "$display_sleep_allow"
203 | fi
204 | }
205 |
206 | INPUT="$1"
207 | main
208 |
--------------------------------------------------------------------------------
/src/dose_filter.zsh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh --no-rcs
2 |
3 | INPUT="$1"
4 | STATE=$(osascript -e 'tell application "Amphetamine" to return session is active' 2>/dev/null)
5 |
6 | if [[ "$STATE" != "true" ]]; then
7 | echo '{"items":[{"title":"Turn On","subtitle":"Prevent sleep indefinitely","arg":"on","icon":{"path":"icon.png"}}]}'
8 | else
9 | echo '{"items":[{"title":"Turn Off","subtitle":"Allow computer to sleep","arg":"off","icon":{"path":"icon.png"}}]}'
10 | fi
11 |
--------------------------------------------------------------------------------
/src/dose_pro.zsh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Use hotkey_value if provided, otherwise use first argument
4 | INPUT="${hotkey_value:-$1}"
5 |
6 | # Default value for display_sleep_allow if not set
7 | display_sleep_allow=${display_sleep_allow:-false}
8 |
9 | if [[ "$INPUT" == "off" ]]; then
10 | osascript -e "tell application \"Amphetamine\" to end session"
11 | echo "Amphetamine deactivated."
12 | elif [[ "$INPUT" == "on" ]]; then
13 | # Use display_sleep_allow parameter only when turning on
14 | osascript -e "tell application \"Amphetamine\" to start new session with options {displaySleepAllowed:$display_sleep_allow}"
15 |
16 | # Use parameter expansion for conditional message
17 | display_text=""
18 | [[ "$display_sleep_allow" == "true" ]] && display_text=" (display can sleep)"
19 | echo "Amphetamine activated${display_text}."
20 | else
21 | echo "Error: Invalid input. Use 'on' or 'off'."
22 | exit 1
23 | fi
24 |
--------------------------------------------------------------------------------