npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

folderctl

v1.0.7

Published

A simple folder monitoring daemon for running custom actions on change.

Downloads

15

Readme

Overview

Folder Control (folderctl) is a background daemon for monitoring folders on macOS. It can run custom shell commands when any files or folders change within. This is similar to Apple's Folder Actions system built into the OS, but Folder Control allows for much more customization, including firing events when any file or folder is modified, showing custom notifications, and playing custom sound effects. It also keeps a detailed log file containing every filesystem event, every shell command executed, and the raw output from all commands.

Features

  • Monitor multiple folders and run custom shell scripts on changes within.
  • Assign different actions for handling a change vs delete.
  • Support for folder path and filename filters (match and exclude).
  • Display custom notifications and/or sound effects for each event.
  • Properly detects errors, optionally with custom regular expression match.
  • Optionally run custom shell scripts every minute, hour or day.
  • Optionally run a custom shell script on startup.
  • Detailed log file for debugging / troubleshooting.

Table of Contents

Usage

Prerequisites

You will need to have Node.js installed on your machine before installing Folder Control.

Installation

Use npm to install the module (this ships with Node.js). Note that Folder Control is designed to run as a standalone background daemon, so take care to understand where npm installs software. It is recommended you install the module globally using the -g switch:

sudo npm install -g folderctl

To see where npm installed the package, you can type npm root -g. This is usually /usr/local/lib/node_modules. Once installed globally, you should have a folderctl command in your PATH. Use this to start, stop and otherwise control the daemon. See Command-Line Usage below.

If you want Folder Control to install itself as a LaunchAgent (so it will startup on boot), run this command next:

sudo folderctl boot

Quick Start

A sample configuration file is provided with the module, which you can copy into place for your user with this command:

cp -v `npm root -g`/folderctl/conf/sample-config.json ~/Library/Preferences/folderctl.json

Edit your file to taste, then start the daemon thusly:

folderctl start

Configuration

The configuration for Folder Control is stored in a single JSON file. It should be placed here:

~/Library/Preferences/folderctl.json

Upon initial installation, you will need to create this file. Here is a sample one you can copy from:

{
	"folders": [
		{
			"enabled": true,
			"path": "Dropbox",
			"actions": {
				"changed": {
					"exec": "say \"We changed [filename].\"", 
					"notify": "We changed [filename].",
					"sound": "/System/Library/Sounds/Ping.aiff"
				}
			}
		}
	],
	
	"concurrency": 1,
	"timeout": 30,
	"debounce_ms": 250,
	"filename_exclude": "^\\.",
	
	"salt_string": "",
	"log_dir": "Library/Logs",
	"log_filename": "folderctl.log",
	"log_columns": ["hires_epoch", "date", "hostname", "pid", "component", "category", "code", "msg", "data"],
	"log_crashes": true,
	"crash_filename": "folderctl.crash.log",
	"pid_file": "Library/Logs/folderctl.pid",
	"debug_level": 9
}

The config file is split into three sections: a folders array containing configurations for each of your folders to watch, a set of default properties that all folders share, and some global configuration properties. See below for details on each.

Folders

The folders property should be an array of objects, with each object representing a folder to watch for changes, and fire events on. Here is an example folder:

{
	"enabled": true,
	"path": "Dropbox",
	"actions": {
		"changed": {
			"exec": "say \"We changed [filename].\"", 
			"notify": "We changed [filename].",
			"sound": "/System/Library/Sounds/Ping.aiff"
		}
	}
}

In the above example the Dropbox folder (paths are relative to your home directory) would be monitored for changes to all files and folders within. Upon changes, a shell script would execute (specified by the exec property), a desktop notification would be displayed (using the text in the notify property), and a sound effect would play (specified by the sound property).

Here are all the properties you can define inside each folder:

| Property | Type | Description | |----------|------|-------------| | path | String | (Required) The filesystem path to the folder you want to watch. Paths are relative to your home directory. | | actions | Object | (Required) An object containing actions to take for specific events. See below for details. | | enabled | Boolean | (Optional) Enables or disables the folder monitor. If this property is missing the folder is enabled by default. | | concurrency | Integer | (Optional) The number of concurrent threads to use when executing your commands (defaults to 1). | | timeout | Integer | (Optional) The maximum number of seconds to allow your commands to take, before timing out (defaults to 30). | | debounce_ms | Integer | (Optional) The number of milliseconds to delay raw filesystem events before taking action (defaults to 250 ms). | | filename_include | String | (Optional) A regular expression which limits file and folder names (defaults to .+, i.e. include everything). | | filename_exclude | String | (Optional) A regular expression which filters out file and folder names (defaults to (?!), i.e. don't exclude anything). | | path_include | String | (Optional) A regular expression which limits file and folder paths (defaults to .+, i.e. include everything). | | path_exclude | String | (Optional) A regular expression which filters out file and folder paths (defaults to (?!), i.e. don't exclude anything). | | shell | String | (Optional) The shell interpreter for running your custom commands (defaults to /bin/bash). | | env | Object | (Optional) Additional environment variables to pass down to your commands. | | cooldown | Integer | (Optional) The cooldown (minimum idle time) in seconds before allowing scheduled events to run (defaults to 60). |

Actions

The actions object can define a number of events to listen for, and fire events on. Here are the available actions you can define in each folder:

| Action | Description | |--------|-------------| | changed | This event will fire for every change, including new files / folders, modified files / folders, and renamed files / folders. | | deleted | This event will fire for every deletion, including deleted files and folders. | | startup | This event will fire once upon startup. | | minute | This event will fire once every minute, on the minute. | | hour | This event will fire every hour, on the hour. | | day | This event will fire once a day at midnight. |

Each action should be an object that describes what to do for the event. Here is an example of an action configuration:

"changed": {
	"exec": "say \"We changed [filename].\"", 
	"notify": "We changed [filename].",
	"sound": "/System/Library/Sounds/Ping.aiff"
}

Here are the available properties you can define per action:

| Property | Type | Description | |----------|------|-------------| | exec | String | (Required) The shell command(s) to execute for the event. This is piped to a shell interpreter as STDIN, so you can specify an entire multi-line shell script here (just make sure you follow proper JSON string escaping). | | notify | String | (Optional) Optionally display a OS desktop notification upon completion of the shell command. Specify any custom text to display in the notification body. | | icon | String | (Optional) Optionally customize the icon shown in the notification (defaults to the Folder Control Icon). | | sound | String | (Optional) Optionally play a custom sound effect upon completion of the shell command. | | success_match | String | (Optional) Optionally specify a regular expression to detect a "successful" command by matching against its output. | | error_match | String | (Optional) Optionally specify a regular expression to detect an error in your command by matching against its output. |

Macros

The exec and notify properties both allow for macro expansion using a special [square_bracket] syntax. The following macros are available:

| Macro | Description | |-------|-------------| | [action] | The name of the event that is being executed (e.g. changed, deleted, etc.). | | [path] | The full path to the file or folder being acted upon. | | [file] | A partial path to the file or folder being acted upon, relative to the base path in the base folder's configuration. | | [filename] | Just the filename (or folder name) of the item being acted upon. | | [filename_urlsafe] | A URL-safe version of the filename (or folder name), with all characters besides alphanumerics, dash (-) and period (.) changed to underscores (_). | | [dirname] | A partial path to the parent folder of the item being acted upon, relative to the base path in the base folder's configuration. | | [hash] | A unique 16-character hash generated using the full file path and a salt_string you can customize. | | [random] | A randomly generated 16-character hash (different every time). |

Note that the special startup, minute, hour and day actions only populate the [action] and [path] macros (the path will be set to the base folder path).

Folder Defaults

These properties can be defined at the top level of the JSON configuration file. They are defaults for each of your folder configurations.

| Property | Type | Description | |----------|------|-------------| | concurrency | Integer | The number of concurrent threads to use when executing your commands. This defaults to 1, and can be overridden per folder. | | timeout | Integer | The maximum number of seconds to allow your commands to take, before timing out. This defaults to 30 seconds, and can be overridden per folder. | | debounce_ms | Integer | The number of milliseconds to delay raw filesystem events before taking action. This defaults to 250 ms, and can be overridden per folder. | | filename_include | String | An optional regular expression which limits file and folder names (only those that match are included). This defaults to .+ (match anything), and can be overridden per folder. | | filename_exclude | String | An optional regular expression which filters out file and folder names (those that match are excluded). This defaults to (?!) (never match), and can be overridden per folder. | | path_include | String | An optional regular expression which limits file and folder paths (only those that match are included). This defaults to .+ (match anything), and can be overridden per folder. | | path_exclude | String | An optional regular expression which filters out file and folder paths (those that match are excluded). This defaults to (?!) (never match), and can be overridden per folder. | | shell | String | The shell interpreter for running your custom commands. This defaults to /bin/bash, and can be overridden per folder. | | env | Object | Optionally include additional environment variables to pass down to your commands. These can also be specified in each folder, and they are merged with this one. | | cooldown | Integer | The cooldown (minimum idle time) in seconds before allowing scheduled events to run. This defaults to 60 seconds, and can be overridden per folder. |

Global Configuration

Here are all the top-level global configuration properties which are not folder specific.

| Property | Type | Description | |----------|------|-------------| | salt_string | String | An optional salt string used to compute unique hashes for each file or folder. | | log_dir | String | The directory in which to place our log files, relative to your home directory. | | log_filename | String | The filename of the Folder Control log file. | | log_columns | Array | An array of log columns to include in the event log. | | log_crashes | Boolean | If set to true, Folder Control will log crashes. | | crash_filename | String | The filename of the crash log, should a crash occur. | | pid_file | String | Path to the PID file used by the control script to start/stop the daemon. Please do not change this. | | debug_level | Integer | A verbosity control for the log file, where 1 is quiet and 10 is very loud indeed. |

See Logging below for more on the Folder Control log.

Recipes

Here are a few recipes, i.e. example folder configurations you can copy or learn from.

Basic Folder Sync

Here is a simple recipe that keeps my local Documents/Notes folder one-way synced with a backup machine:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\""
		}
	}
}

Here we make use of the special [file] macro, which is a relative path to the file (or folder) that changed, relative to the base directory. This way we only have to sync something that was added or modified, and not have to re-sync the entire folder on every change.

Note that I am using the rsync R (--relative) flag, which preserves the relative path to the file on the remote side, and automatically creates any necessary parent folders.

In order for rsync to work, you will need to create an SSH keypair and copy your public key to the remote backup server.

Sync on Startup

You can use the startup action to perform a pre-sync of your entire folder on startup, for example:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"startup": {
			"exec": "rsync -av ~/Documents/Notes/ backup.local:~/Documents/Notes/"
		},
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\""
		}
	}
}

This would sync the entire Documents/Notes folder on startup, rather than just a changed file or folder. This is in case I made any changes while offline or something. Note that this recipe doesn't take into account changes on the remote side. See Two-Way Sync below.

Sync Deletes

Synchronizing deletes requires special care, and a special deleted action with a separate shell command:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\""
		},
		"deleted": {
			"exec": "rsync -av --delete \"[dirname]/\" \"backup.local:~/Documents/Notes/[dirname]/\""
		}
	}
}

Here we are using the special [dirname] macro, which is a relative path to the parent folder of the item that was deleted. This is important because rsync cannot delete a remote file by pointing to the file itself -- it has to point to the parent directory containing the deleted file or folder.

Copy Screenshot URL to Clipboard

Here is a recipe that watches for new macOS screenshots, which typically arrive on your Desktop. Then, it copies each new screenshot up to an Amazon S3 bucket, and, assuming your bucket is also a website, it copies a URL to the clipboard for the screenshot, and moves the screenshot off the desktop:

{
	"enabled": true,
	"path": "Desktop",
	"filename_match": "^Screen\s+Shot.+\\.png$",
	"actions": {
		"changed": {
			"exec": [
				"aws s3 cp \"[file]\" \"s3://my-s3-bucket/screenshots/[filename_urlsafe]\"",
				"echo -n \"https://my-s3-domain.com/screenshots/[filename_urlsafe]\" | pbcopy",
				"mv \"[file]\" Documents/Screenshots/"
			]
		}
	}
}

So, there is a lot to digest here. First, note that we are using the filename_match feature to only trigger on files with names starting with Screen Shot, and ending with .png. Also, note that the exec property is actually an array in this case, with 3 separate shell commands:

[
	"aws s3 cp \"[file]\" \"s3://my-s3-bucket/screenshots/[filename_urlsafe]\"",
	"echo -n \"https://my-s3-domain.com/screenshots/[filename_urlsafe]\" | pbcopy",
	"mv \"[file]\" Documents/Screenshots/"
]

This format is really just for convenience -- you could achieve the same thing by embedding escaped EOL (\n) characters into one exec string, or concatenating the shell commands together with &&. Using an array just looks prettier in JSON format. So here is what is happening, line by line:

  • We run the AWS command-line utility to upload the screenshot to S3, using the special [filename_urlsafe] macro to construct a safe S3 path (i.e. URL-friendly filename).
  • We are using the bash echo command to construct and print the URL, and then piping that to the macOS pbcopy command, which copies text to the clipboard.
  • Finally, we are moving the screenshot file off of the Desktop, and into Documents/Screenshots/.

In order for all this to work, you will need an Amazon AWS account, an S3 bucket with static website hosting enabled, the AWS CLI utility installed, and your AWS credentials available locally for your user.

Copy Hashed URL to Clipboard

If you want slightly more anonymized S3 paths and URLs, you can use the special [hash] macro. This is similar to what Dropbox does when you right-click a file and select "Copy Public URL". The [hash] is a 16-character signature generated from the full local file path, and a salt_string if provided in your configuration. Example use:

{
	"enabled": true,
	"path": "Dropbox/Public",
	"actions": {
		"changed": {
			"exec": [
				"aws s3 cp \"[file]\" \"s3://my-s3-bucket/public/[hash]/[filename_urlsafe]\"",
				"echo -n \"https://my-s3-domain.com/public/[hash]/[filename_urlsafe]\" | pbcopy"
			]
		},
		"deleted": {
			"exec": "aws s3 rm \"s3://my-s3-domain.com/public/[hash]/[filename_urlsafe]\""
		}
	}
}

The idea here is to insert a somewhat "random" element in the S3 path and URL, so it is just a bit more secure than linking directly to a file by its name. Generally this would be for publicly-facing S3 buckets and URLs. We use a hash so the same value will be computed if the file is deleted later -- notice we are also listening for the deleted event and performing a remote S3 delete on the file, also using the [hash] macro. This works because the [hash] will be exactly the same for the same file path and salt string.

To make this system more secure, it is recommended that you provide a unique salt_string in your configuration file.

Displaying Notifications

To display a notification when your shell script completes, you can set the notify property in one or more of your actions. The value should be a string containing the text to display, and you can use [macros] here as well. Example:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\"",
			"notify": "Item successfully backed up: [filename]"
		}
	}
}

In this example we would display a notification with the text: Item successfully backed up: followed by the filename of the file (or folder) that was changed. You can use any of the macros described above in Macros.

You can optionally customize the icon that accompanies the notification. It defaults to the Folder Control icon, but you can replace it with your own icon by including an icon property. Make sure you specify a full path. Example:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\"",
			"notify": "File successfully backed up: [filename]",
			"icon": "/path/to/your/custom-icon.png"
		}
	}
}

Note that the notification is only shown when your shell command completes successfully.

Playing Sounds

To play a sound effect when your shell script completes, you can set the sound property in one or more of your actions. The value should be a full filesystem path to the sound file (AIFF, MP3 and WAV are all supported). Example use:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\"",
			"sound": "/System/Library/Sounds/Ping.aiff"
		}
	}
}

This would play the Ping.aiff sound effect after each file or folder finished syncing. You can combine a sound effect with a notification, or use each one separately.

The following sounds are available in your macOS installation:

/System/Library/Sounds/Basso.aiff
/System/Library/Sounds/Blow.aiff
/System/Library/Sounds/Bottle.aiff
/System/Library/Sounds/Frog.aiff
/System/Library/Sounds/Funk.aiff
/System/Library/Sounds/Glass.aiff
/System/Library/Sounds/Hero.aiff
/System/Library/Sounds/Morse.aiff
/System/Library/Sounds/Ping.aiff
/System/Library/Sounds/Pop.aiff
/System/Library/Sounds/Purr.aiff
/System/Library/Sounds/Sosumi.aiff
/System/Library/Sounds/Submarine.aiff
/System/Library/Sounds/Tink.aiff

Note that the sound only plays when your shell command completes successfully.

Excluding Hidden Files

To exclude certain files, use the filename_exclude property in your top-level or folder-specific configuration. Hidden files are typically prefixed with a period (.), which includes the dreaded macOS .DS_Store files, so you can use this regular expression to skip them all:

"filename_exclude": "^\\."

To exclude hidden folders as well, include the path_exclude property, and set it to a regular expression like this:

"path_exclude": "/\\."

Note that the path_exclude is matched against the full path to the file or folder being acted upon, hence we are matching /. (slash followed by period) anywhere in the string.

These two properties can be defined in your top-level configuration as defaults, or specified per folder.

Detecting Specific Errors

In most cases Folder Control will automatically catch and notify you about errors in your shell scripts -- that is, if the command fails with a non-zero exit code. But sometimes you may need to detect your own errors by matching a string against the command's output. To do this, include the success_match and/or error_match properties in your actions, and set them to regular expressions. Example use:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\"",
			"error_match": "connection unexpectedly closed"
		}
	}
}

This would trigger an error if the output from the command included the string "connection unexpectedly closed", even if the command returned a zero (successful) exit code. The success_match property works in the other direction -- if specified, the output must match your pattern for the command to be considered a success. If both are specified, both are checked against the command output.

Both success_match and error_match are matched against STDOUT and STDERR, so you don't need to do the 2>&1 redirect dance.

Two-Way Sync

Folder Control does not support true two-way sync, unless you use some sort of 3rd party utility such as Unison or osync. The following is just one way to achieve a very poor man's bi-directional one-way sync.

To sync in both directions, you can use one of the scheduler actions. Meaning, do the sync from your local machine to your remote backup on change as per usual, but then also fire off a routine reverse sync every minute, hour or day. Example use:

{
	"enabled": true,
	"path": "Documents/Notes",
	"actions": {
		"changed": {
			"exec": "rsync -avR \"[file]\" \"backup.local:~/Documents/Notes/\""
		},
		"minute": {
			"exec": "rsync -av \"backup.local:~/Documents/Notes/\" \"[path]/\""
		}
	}
}

The scheduler actions minute, hour and day all follow a "cooldown", which means if any change events occur (which fires off a forward sync in this case) the scheduler actions will all be skipped up until a cooldown period has elapsed. The default cooldown is 60 seconds, but you can configure it by setting the cooldown config property.

Note that the only custom macros available in the scheduler actions are [action] and [path] (the path will be set to the base folder path).

Custom Environment Variables

If your shell commands require any custom environment variables, you can supply them in a env configuration property, which can be defined globally (top-level) or per folder. For example, this can be used to reset the PATH:

"env": {
	"PATH": "/usr/bin:/usr/local/bin:/Users/jhuckaby/bin"
}

These environments variables will be merged in with the existing ones when the Folder Control daemon was started, and then passed down to your shell processes.

Command-Line Usage

Folder Control comes with a simple command-line control script called folderctl. It should already be available in your PATH, assuming you installed the module via sudo npm install -g folderctl. It accepts a single command-line argument to start, stop, and a few other things. Examples:

folderctl start
folderctl stop
folderctl restart

Here is the full command list:

| Command | Description | |---------|-------------| | help | Show usage information. | | start | Start Folder Control as a background service. | | stop | Stop Folder Control and wait until it actually exits. | | restart | Calls stop, then start (hard restart). | | status | Checks whether Folder Control is currently running. | | boot | Install Folder Control as a startup service. | | unboot | Remove Folder Control from the startup services. | | debug | Start the service in debug mode (see Debugging below). |

Debugging

To start Folder Control in debug mode, issue this command:

folderctl debug

This will start the service as a foreground process (not a daemon), and echo the event log straight to the console. This is a great way to troubleshoot issues. Hit Ctrl-C to exit.

Upgrading

To upgrade to the latest Folder Control version, you can use the sudo npm update -g command. Your user configuration file will not be touched. Assuming you installed Folder Control globally, and it is currently running, then issue these commands to upgrade to the latest stable:

folderctl stop
sudo npm update -g folderctl
folderctl start

Uninstall

Folder Control isn't for you? No problem, you can remove it with these commands:

folderctl stop
sudo folderctl unboot
sudo npm remove -g folderctl

To remove all traces of the software, you may want to delete these files as well:

rm -v ~/Library/Preferences/folderctl.json
rm -v ~/Library/Logs/folderctl*

Logging

Folder Control uses the logging system built into pixl-server. Essentially there is one combined "event log" which contains debug messages and errors. The component column will be set to either FolderControl, or one of your own folder paths. Most debug messages will be folder-specific.

By default it will log to ~/Library/Logs/folderctl.log.

The general logging configuration is controlled by these three top-level global properties:

| Property Name | Type | Description | |---------------|------|-------------| | log_dir | String | Directory path where event log will be stored. Can be a fully-qualified path, or relative from your home directory. | | log_filename | String | Event log filename, joined with log_dir. | | debug_level | Integer | Debug logging level, larger numbers are more verbose, 1 is quietest, 10 is loudest. |

Log entries with the category set to debug are debug messages, and have a verbosity level from 1 to 10.

Here is an example log excerpt showing a typical startup with one folder. In all these log examples the first 3 columns (hires_epoch, date, hostname and pid) are omitted for display purposes. The columns shown are component, category, code, msg, and data.

[FolderControl][debug][2][Spawning background daemon process (PID 690 will exit)][["/usr/local/bin/node","/Users/jhuckaby/node_modules/folderctl/lib/main.js"]]
[FolderControl][debug][1][FolderControl v1.0.0 Starting Up][{"pid":693,"ppid":1,"node":"v10.14.1","arch":"x64","platform":"darwin","argv":["/usr/local/bin/node","/Users/jhuckaby/node_modules/folderctl/lib/main.js"],"execArgv":[]}]
[FolderControl][debug][9][Writing PID File: Library/Logs/folderctl.pid: 693][]
[FolderControl][debug][9][Confirmed PID File contents: Library/Logs/folderctl.pid: 693][]
[FolderControl][debug][2][Daemon PID: 693][]
[FolderControl][debug][3][Starting component: FolderControl][]
[FolderControl][debug][3][FolderControl engine starting up][["/usr/local/bin/node","/Users/jhuckaby/node_modules/folderctl/lib/main.js"]]
[FolderControl][debug][4][Setting up folder watch: Dropbox][{"path":"Dropbox","actions":{"changed":{"exec":"say \"We changed [filename].\"","notify":"We changed [filename].","sound":"/System/Library/Sounds/Ping.aiff"}}}]
[FolderControl][debug][2][Startup complete, entering main loop][]

And here is an example log excerpt for a folder change event:

[Dropbox][debug][9][Raw FS Event: /Users/jhuckaby/Dropbox/PlainText/Notes.txt][]
[Dropbox][debug][8][Processing normalized change event][["/Users/jhuckaby/Dropbox/PlainText/Notes.txt"]]
[Dropbox][debug][9][Dequeuing event: changed][{"file":"/Users/jhuckaby/Dropbox/PlainText/Notes.txt"}]
[Dropbox][debug][9][Executing shell script for changed: say "We changed Notes.txt."][]
[Dropbox][debug][9][Raw command output:  ][{"code":0,"signal":null}]
[Dropbox][debug][9][Command was successful][]
[Dropbox][debug][9][Displaying notification: We changed Notes.txt.][]
[Dropbox][debug][9][Playing sound: /System/Library/Sounds/Ping.aiff][]

If you are concerned about log file size, and/or you run Folder Control with a high debug_level (verbosity), you might want to enable log rotation. This can be done easily on macOS by creating the following file:

sudo vi /etc/newsyslog.d/folderctl.conf

And then paste in these contents:

# logfilename                      [owner:group]    mode count size when  flags [/pid_file] [sig_num]
/Users/*/Library/Logs/folderctl.log                 644  5     *    $D0   J

License

The MIT License (MIT)

Copyright (c) 2019 Joseph Huckaby.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.