auto-terminal-profile 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ #! /usr/bin/env node
2
+
3
+ import {disableAutomaticSwitching} from '../functions/index.js';
4
+
5
+ export async function disable() {
6
+ await disableAutomaticSwitching();
7
+
8
+ console.log('Automatic switching disabled');
9
+ }
@@ -0,0 +1,24 @@
1
+ import {config} from '../config.js';
2
+ import {packageJson} from '../constants/index.js';
3
+ import {enableAutomaticSwitching} from '../functions/index.js';
4
+
5
+ export async function enable({darkProfile, lightProfile}) {
6
+ if (!darkProfile && !config.darkProfile) {
7
+ throw new Error(
8
+ `Dark profile must be specified with --dark-profile or previously set with \`${packageJson.name} set-dark-mode\``,
9
+ );
10
+ }
11
+
12
+ if (!lightProfile && !config.lightProfile) {
13
+ throw new Error(
14
+ `Light profile must be specified with --light-profile or previously set with \`${packageJson.name} set-light-mode\``,
15
+ );
16
+ }
17
+
18
+ if (darkProfile) config.darkProfile = darkProfile;
19
+ if (lightProfile) config.lightProfile = lightProfile;
20
+
21
+ await enableAutomaticSwitching();
22
+
23
+ console.log('Automatic switching enabled');
24
+ }
@@ -0,0 +1,5 @@
1
+ export {disable} from './disable.js';
2
+ export {enable} from './enable.js';
3
+ export {setModeProfile} from './set-mode-profile.js';
4
+ export {status} from './status.js';
5
+ export {updateProfile} from './update-profile.js';
@@ -0,0 +1,7 @@
1
+ import {config} from '../config.js';
2
+
3
+ export function setModeProfile({mode, profile}) {
4
+ config[`${mode}Profile`] = profile;
5
+
6
+ console.log(`${mode} mode profile set to '${profile}'`);
7
+ }
@@ -0,0 +1,12 @@
1
+ import {isAutomaticSwitchingEnabled} from '../functions/index.js';
2
+ import {config} from '../config.js';
3
+
4
+ export async function status() {
5
+ console.log(
6
+ `automatic switching : ${
7
+ (await isAutomaticSwitchingEnabled()) ? 'enabled' : 'disabled'
8
+ }`,
9
+ );
10
+ console.log(`dark profile : ${config.darkProfile}`);
11
+ console.log(`light profile : ${config.lightProfile}`);
12
+ }
@@ -0,0 +1,17 @@
1
+ import darkMode from 'dark-mode';
2
+ import {setTerminalProfile} from 'terminal-profile';
3
+ import {config} from '../config.js';
4
+
5
+ export async function updateProfile() {
6
+ if (!config.darkProfile) {
7
+ throw new Error('Dark profile not set');
8
+ }
9
+
10
+ if (!config.lightProfile) {
11
+ throw new Error('Light profile not set');
12
+ }
13
+
14
+ const mode = (await darkMode.isEnabled()) ? 'dark' : 'light';
15
+
16
+ await setTerminalProfile(config[`${mode}Profile`]);
17
+ }
package/cli.js ADDED
@@ -0,0 +1,60 @@
1
+ #! /usr/bin/env node
2
+
3
+ import {program} from 'commander';
4
+ import {packageJson} from './constants/index.js';
5
+ import {
6
+ disable,
7
+ enable,
8
+ setModeProfile,
9
+ status,
10
+ updateProfile,
11
+ } from './actions/index.js';
12
+
13
+ program
14
+ .name(packageJson.name)
15
+ .description(packageJson.description)
16
+ .version(packageJson.version);
17
+
18
+ program
19
+ .command('disable')
20
+ .description(
21
+ 'Disable automatic macOS Terminal profile switching based on system dark / light mode',
22
+ )
23
+ .action(disable);
24
+
25
+ program
26
+ .command('enable')
27
+ .description(
28
+ 'Enable automatic macOS Terminal profile switching based on system dark / light mode',
29
+ )
30
+ .option(
31
+ '--dark-profile <profile>',
32
+ 'dark profile name, for example "One Dark"',
33
+ )
34
+ .option(
35
+ '--light-profile <profile>',
36
+ 'light profile name, for example "One Light"',
37
+ )
38
+ .action(enable);
39
+
40
+ for (const mode of ['dark', 'light']) {
41
+ program
42
+ .command(`set-${mode}-profile`)
43
+ .description(`Set the Terminal profile to use in ${mode} mode`)
44
+ .argument('<profile>')
45
+ .action((profile) => setModeProfile({mode, profile}));
46
+ }
47
+
48
+ program
49
+ .command('status')
50
+ .description('Show status and configuration')
51
+ .action(status);
52
+
53
+ program
54
+ .command('update-profile')
55
+ .description(
56
+ 'Update the profile of currently running Terminal windows / tabs',
57
+ )
58
+ .action(updateProfile);
59
+
60
+ program.parse();
package/config.js ADDED
@@ -0,0 +1,43 @@
1
+ import Conf from 'conf';
2
+ import {readPackageUp} from 'read-pkg-up';
3
+
4
+ const {packageJson} = await readPackageUp({cwd: new URL('.', import.meta.url)});
5
+
6
+ const conf = new Conf({projectName: packageJson.name});
7
+
8
+ class Config {
9
+ keys = {
10
+ darkProfile: 'darkProfile',
11
+ lightProfile: 'lightProfile',
12
+ };
13
+
14
+ /**
15
+ * @return {string}
16
+ */
17
+ get darkProfile() {
18
+ return conf.get(this.keys.darkProfile);
19
+ }
20
+
21
+ /**
22
+ * @param {string} profile
23
+ */
24
+ set darkProfile(profile) {
25
+ conf.set(this.keys.darkProfile, profile);
26
+ }
27
+
28
+ /**
29
+ * @return {string}
30
+ */
31
+ get lightProfile() {
32
+ return conf.get(this.keys.lightProfile);
33
+ }
34
+
35
+ /**
36
+ * @param {string} profile
37
+ */
38
+ set lightProfile(profile) {
39
+ conf.set(this.keys.lightProfile, profile);
40
+ }
41
+ }
42
+
43
+ export const config = new Config();
@@ -0,0 +1,2 @@
1
+ export {launchAgentPlistFilePath} from './launch-agent-plist-file-path.js';
2
+ export {packageJson} from './package-json.js';
@@ -0,0 +1,5 @@
1
+ import untildify from 'untildify';
2
+
3
+ export const launchAgentPlistFilePath = untildify(
4
+ '~/Library/LaunchAgents/ke.bou.dark-mode-notify.plist',
5
+ );
@@ -0,0 +1,5 @@
1
+ import {readPackageUp} from 'read-pkg-up';
2
+
3
+ export const {packageJson} = await readPackageUp({
4
+ cwd: new URL('.', import.meta.url),
5
+ });
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Bouke van der Bijl
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.
@@ -0,0 +1,44 @@
1
+ # dark-mode-notify
2
+
3
+ This small Swift program will run a command whenever the dark mode status changes on macOS. You can use it to change your vim color config automatically for example.
4
+
5
+ ## Usage
6
+
7
+ You can run it directly by doing `./dark-mode-notify.swift <program>`.
8
+
9
+ Alternatively you can compile it by doing `swiftc dark-mode-notify.swift -o /usr/local/bin/dark-mode-notify`.
10
+
11
+ The program will be run immediately when the command starts, and every time the OS goes from dark mode to light mode or back. The environment variable `DARKMODE` will be set to either `1` or `0`.
12
+
13
+ ## Background agent
14
+
15
+ To keep this program running in the background, compile the binary to somewhere and create the following file at `~/Library/LaunchAgents/ke.bou.dark-mode-notify.plist`. Don't forget to replace the arguments and the path to the logs (which comes in handy for debugging)
16
+
17
+ ```xml
18
+ <?xml version="1.0" encoding="UTF-8"?>
19
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
20
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
21
+ <plist version="1.0">
22
+ <dict>
23
+ <key>Label</key>
24
+ <string>ke.bou.dark-mode-notify</string>
25
+ <key>KeepAlive</key>
26
+ <true/>
27
+ <key>StandardErrorPath</key>
28
+ <string>----Path to a location----/dark-mode-notify-stderr.log</string>
29
+ <key>StandardOutPath</key>
30
+ <string>----Path to a location----/dark-mode-notify-stdout.log</string>
31
+ <key>ProgramArguments</key>
32
+ <array>
33
+ <string>/usr/local/bin/dark-mode-notify</string>
34
+ <string>--- Path to your script ---</string>
35
+ </array>
36
+ </dict>
37
+ </plist>
38
+ ```
39
+
40
+ Then `launchctl load -w ~/Library/LaunchAgents/ke.bou.dark-mode-notify.plist` will keep it running on boot.
41
+
42
+ ## Credit
43
+
44
+ This script is a lightly modified version of https://github.com/mnewt/dotemacs/blob/master/bin/dark-mode-notifier.swift
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env swift
2
+
3
+ // Run as ./notify.swift <program to run when dark mode changes>
4
+ // The program will have the DARKMODE env flag set to 1 or 0
5
+ // You can also compile with:
6
+ // swiftc notify.swift -o notify
7
+ // And run the binary directly
8
+ // Most credit goes to https://github.com/mnewt/dotemacs/blob/master/bin/dark-mode-notifier.swift
9
+
10
+ import Cocoa
11
+
12
+ @discardableResult
13
+ func shell(_ args: [String]) -> Int32 {
14
+ let task = Process()
15
+ let isDark = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark"
16
+ var env = ProcessInfo.processInfo.environment
17
+ env["DARKMODE"] = isDark ? "1" : "0"
18
+ task.environment = env
19
+ task.launchPath = "/usr/bin/env"
20
+ task.arguments = args
21
+ task.standardError = FileHandle.standardError
22
+ task.standardOutput = FileHandle.standardOutput
23
+ task.launch()
24
+ task.waitUntilExit()
25
+ return task.terminationStatus
26
+ }
27
+
28
+ let args = Array(CommandLine.arguments.suffix(from: 1))
29
+ shell(args)
30
+
31
+ DistributedNotificationCenter.default.addObserver(
32
+ forName: Notification.Name("AppleInterfaceThemeChangedNotification"),
33
+ object: nil,
34
+ queue: nil) { (notification) in
35
+ shell(args)
36
+ }
37
+
38
+ NSApplication.shared.run()
@@ -0,0 +1,9 @@
1
+ import {unlink} from 'node:fs/promises';
2
+ import {execa} from 'execa';
3
+ import {launchAgentPlistFilePath} from '../constants/index.js';
4
+
5
+ export async function disableAutomaticSwitching() {
6
+ await execa('launchctl', ['unload', '-w', launchAgentPlistFilePath]);
7
+
8
+ await unlink(launchAgentPlistFilePath);
9
+ }
@@ -0,0 +1,13 @@
1
+ import {writeFile} from 'node:fs/promises';
2
+ import {execa} from 'execa';
3
+ import {launchAgentPlistFilePath} from '../constants/index.js';
4
+ import {getLaunchAgentPlistFileContents} from './get-launch-agent-plist-file-contents.js';
5
+
6
+ export async function enableAutomaticSwitching() {
7
+ await writeFile(
8
+ launchAgentPlistFilePath,
9
+ await getLaunchAgentPlistFileContents(),
10
+ );
11
+
12
+ await execa('launchctl', ['load', '-w', launchAgentPlistFilePath]);
13
+ }
@@ -0,0 +1,24 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import {fileURLToPath} from 'node:url';
3
+ import process from 'node:process';
4
+ import pupa from 'pupa';
5
+ import envPaths from 'env-paths';
6
+ import {packageJson} from '../constants/index.js';
7
+
8
+ export async function getLaunchAgentPlistFileContents() {
9
+ return pupa(
10
+ await readFile(new URL('../templates/launch-agent.xml', import.meta.url), {
11
+ encoding: 'utf8',
12
+ }),
13
+ {
14
+ autoTerminalProfilePath: fileURLToPath(
15
+ new URL('../cli.js', import.meta.url),
16
+ ),
17
+ darkModeNotifyPath: fileURLToPath(
18
+ new URL('../dark-mode-notify/dark-mode-notify.swift', import.meta.url),
19
+ ),
20
+ logPath: envPaths(packageJson.name).log,
21
+ path: process.env.PATH,
22
+ },
23
+ );
24
+ }
@@ -0,0 +1,4 @@
1
+ export {enableAutomaticSwitching} from './enable-automatic-switching.js';
2
+ export {disableAutomaticSwitching} from './disable-automatic-switching.js';
3
+ export {getLaunchAgentPlistFileContents} from './get-launch-agent-plist-file-contents.js';
4
+ export {isAutomaticSwitchingEnabled} from './is-automatic-switching-enabled.js';
@@ -0,0 +1,32 @@
1
+ import {existsSync as exists} from 'node:fs';
2
+ import {execa} from 'execa';
3
+ import {launchAgentPlistFilePath} from '../constants/index.js';
4
+
5
+ /**
6
+ * @return {Promise<boolean>}
7
+ */
8
+ async function isDarkModeNotifyRunning() {
9
+ const {stdout} = await execa('launchctl', ['list']);
10
+
11
+ return stdout.includes('ke.bou.dark-mode-notify');
12
+ }
13
+
14
+ /**
15
+ * @return {Promise<boolean>}
16
+ */
17
+ export async function isAutomaticSwitchingEnabled() {
18
+ const _isDarkModeNotifyRunning = await isDarkModeNotifyRunning();
19
+ const plistFileExists = exists(launchAgentPlistFilePath);
20
+
21
+ if (_isDarkModeNotifyRunning && !plistFileExists) {
22
+ throw new Error(
23
+ `Automatic switching is running, but the launch agent plist file (${launchAgentPlistFilePath}) doesn't exist`,
24
+ );
25
+ } else if (!_isDarkModeNotifyRunning && plistFileExists) {
26
+ throw new Error(
27
+ `Automatic switching is not running, but the launch agent plist file (${launchAgentPlistFilePath}) exists`,
28
+ );
29
+ }
30
+
31
+ return _isDarkModeNotifyRunning && plistFileExists;
32
+ }
package/license.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Patrik Csak
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.
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "auto-terminal-profile",
3
+ "version": "1.0.0",
4
+ "description": "Automatically switch the macOS Terminal profile based on the system-wide dark / light appearance mode",
5
+ "keywords": [
6
+ "macos",
7
+ "terminal",
8
+ "light mode",
9
+ "dark mode",
10
+ "profile"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Patrik Csak <p@trikcsak.com>",
14
+ "bin": "./cli.js",
15
+ "repository": "github:ptrkcsk/auto-terminal-profile",
16
+ "scripts": {
17
+ "format": "xo --fix",
18
+ "test": "xo"
19
+ },
20
+ "dependencies": {
21
+ "commander": "^9.3.0",
22
+ "conf": "^10.1.2",
23
+ "dark-mode": "^4.0.0",
24
+ "env-paths": "^3.0.0",
25
+ "execa": "^6.1.0",
26
+ "pupa": "^3.1.0",
27
+ "read-pkg-up": "^9.1.0",
28
+ "terminal-profile": "^1.0.1",
29
+ "untildify": "^4.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "xo": "^0.50.0"
33
+ },
34
+ "engines": {
35
+ "node": "14 - 16"
36
+ },
37
+ "os": [
38
+ "darwin"
39
+ ],
40
+ "type": "module"
41
+ }
package/readme.md ADDED
@@ -0,0 +1,44 @@
1
+ # `auto-terminal-profile`
2
+
3
+ Automatically switch the macOS Terminal profile based on the system-wide dark / light appearance mode
4
+
5
+ ![auto-terminal-profile demonstration screen recording](./documentation/demo.gif)
6
+
7
+ ## Prerequisites
8
+
9
+ - [Node.js](https://nodejs.org/en/) 14-16
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install --global auto-terminal-profile
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```sh
20
+ auto-terminal-profile --help
21
+ ```
22
+
23
+ ```
24
+ Usage: auto-terminal-profile [options] [command]
25
+
26
+ Automatically switch the macOS Terminal profile based on the system-wide dark / light appearance mode
27
+
28
+ Options:
29
+ -V, --version output the version number
30
+ -h, --help display help for command
31
+
32
+ Commands:
33
+ disable Disable automatic macOS Terminal profile switching based on system dark / light mode
34
+ enable [options] Enable automatic macOS Terminal profile switching based on system dark / light mode
35
+ set-dark-profile <profile> Set the Terminal profile to use in dark mode
36
+ set-light-profile <profile> Set the Terminal profile to use in light mode
37
+ status Show status and configuration
38
+ update-profile Update the profile of currently running Terminal windows / tabs
39
+ help [command] display help for command
40
+ ```
41
+
42
+ ## Acknowledgements
43
+
44
+ Thanks to [Fatih Arslan](https://github.com/fatih) for his article [*Automatic dark mode for terminal applications*](https://arslan.io/2021/02/15/automatic-dark-mode-for-terminal-applications/) and [Bouke van der Bijl](https://github.com/bouk) for his project [dark-mode-notify](https://github.com/bouk/dark-mode-notify), which this project uses
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4
+ <plist version="1.0">
5
+ <dict>
6
+ <key>Label</key>
7
+ <string>ke.bou.dark-mode-notify</string>
8
+ <key>KeepAlive</key>
9
+ <true/>
10
+ <key>StandardErrorPath</key>
11
+ <string>{logPath}/dark-mode-notify-stderr.log</string>
12
+ <key>StandardOutPath</key>
13
+ <string>{logPath}/dark-mode-notify-stdout.log</string>
14
+ <key>EnvironmentVariables</key>
15
+ <dict>
16
+ <key>PATH</key>
17
+ <string>{path}</string>
18
+ </dict>
19
+ <key>ProgramArguments</key>
20
+ <array>
21
+ <string>{darkModeNotifyPath}</string>
22
+ <string>{autoTerminalProfilePath}</string>
23
+ <string>update-profile</string>
24
+ </array>
25
+ </dict>
26
+ </plist>