Creating a Plugin

This guide will help you create a new WakaTime plugin for your text editor/IDE.

Let us know if you're building a WakaTime plugin! We can help you via email!

We can also evangelize and help promote your new plugin to all WakaTime users.

Table of Contents

  1. Getting Started
  2. Plugin Overview
  3. Plugin Initialization
  4. Handling Editor Events
  5. Sending File to wakatime-cli
  6. Debugging
  7. Releasing

Getting Started

A WakaTime plugin is simple… just run the wakatime-cli command and pass it the current file as an argument.

$ wakatime-cli --entity myfile.txt

The code above sends a heartbeat to the WakaTime API and starts logging time.

Note: You might need to add --key XXXX.

WakaTime plugins have two parts:

  1. editor plugin - the core which uses the editor's api and sends the currently focused file to wakatime-cli when the user moves the cursor or types some characters
  2. wakatime-cli - the common command line program which takes a file as input, detects the language and other metadata, then sends that data (aka "heartbeat") to the WakaTime api

Source code for existing WakaTime plugins can be viewed on GitHub.

Plugin Overview

This is a high-level overview of a WakaTime plugin from the time it's loaded, until the editor is exited.

  • Plugin loaded by text editor/IDE, runs plugin's initialization code
  • Initialization code
    • Setup any global variables, like plugin version, editor/IDE version
    • Check for wakatime-cli, or download into ~/.wakatime/ if missing or needs an update
    • Check for api key in ~/.wakatime.cfg, prompt user to enter if does not exist
    • Setup event listeners to detect when current file changes, a file is modified, and a file is saved
  • Current file changed (our file change event listener code is run)
    • go to Send heartbeat function with isWrite false
  • User types in a file (our file modified event listener code is run)
    • go to Send heartbeat function with isWrite false
  • A file is saved (our file save event listener code is run)
    • go to Send heartbeat function with isWrite true
  • Send heartbeat function
    • check lastHeartbeat variable. if isWrite is false, and file has not changed since last heartbeat, and less than 2 minutes since last heartbeat, then return and do nothing
    • run wakatime-cli in background process passing it the current file
    • update lastHeartbeat variable with current file and current time

Plugin Initialization

This happens every time your plugin is loaded, for example when the user opens their text editor/IDE or when the user first installs the plugin.

Examples of init code can be viewed here:

Installing the wakatime-cli dependency

wakatime-cli is a Go command line tool containing common code that every plugin uses. It does things like detect the current project, branch, syntax language, and send heartbeats to the WakaTime api. To send a heartbeat, your plugin should run wakatime-cli in a background process and pass it the absolute path of the current file.

The wakatime-cli binary is downloaded from GitHub releases. We also use the GitHub API to check the latest release version, and update wakatime-cli to the latest release if needed. In your plugin’s init function, check if ~/.wakatime/wakatime-cli-- exists and that it’s version matches the latest GitHub release. If wakatime-cli needs updating or doesn’t exist, download the wakatime-cli zip file for the current OS and architecture from the latest release then unzip it into ~/.wakatime/.

Examples of downloading and unzipping wakatime-cli can be found here:

Listening for Editor Events

The last step when initializing your plugin is setting up event listeners. Most editors support some version of these events:

  • currently focused file has changed (for example, switching from file A to file B)
  • current file was modified
  • a file was saved

These three events are the only ones we care about. When one of these three events are detected, your plugin should send the file to wakatime-cli.

Some examples of listening for these events:

Handling Editor Events

Every event handler should call the same function, sometimes passing the currently focused file as a parameter if needed. The only catch is the File Save event needs to pass a boolean to indicate this heartbeat was triggered from saving to a file.

File Changed

The file changed event detects when the editor focus has changed to a new file. For example, when the user switches to a new tab in their editor.

File Modified

The file modified event should be triggered every time the currently focused file is changed. If your editor only supports detecting the first time a file is modified, you should detect when a user presses a key instead.

File Saved

The file saved event detects when a file's modified contents are written to disk.

Sending File to wakatime-cli

Every event handler should call the same function. This common function should accept one argument, the boolean indicating whether this event was triggered from saving to a file(true) or not(false).

The first thing this function should do is detect the currently focused file. If there is no way to detect the currently focused file, the event handlers can pass the current file as an argument.

Deciding to Send File or Not

This function should decide whether to send the file to wakatime-cli or not, depending on how long it has been since it last sent the same file to wakatime-cli. Here's some example Python showing this decision:

if enoughTimeHasPassed(lastSentTime) or currentlyFocusedFileHasChanged(lastSentFile) or isFileSavedEvent(): sendFileToWakatimeCLI() else: # do nothing pass

The enoughTimeHasPassed function checks if more than 2 minutes have passed since this function has executed last, to prevent sending the currently focused file to wakatime-cli too frequently. In this case, it would check if lastSentTime times 120 seconds is greater than the current time.

The currentlyFocusedFileHasChanged function checks if the absolute path to the currently focused file equals lastSentFile and returns True if it does not equal and False if it is equal.

The isFileSavedEvent function checks the boolean passed from the event handlers, and returns it as-is.

Executing Background Process

Now that we have decided to send the file to wakatime-cli, we need to execute it as a background process and pass the current file as a command line argument. For reference, here is the wakatime-cli help:

$ wakatime-cli [flags] Flags: --alternate-language string Optional alternate language name. Auto-detected language takes priority. --alternate-project string Optional alternate project name. Auto-detected project takes priority. --api-url string API base url used when sending heartbeats and fetching code stats. Defaults to https://api.wakatime.com/api/v1/. --category string Category of this heartbeat activity. Can be "coding", "building", "indexing", "debugging", "communicating", "running tests", "writing tests", "manual testing", "code reviewing", "browsing", or "designing". Defaults to "coding". --config string Optional config file. Defaults to '~/.wakatime.cfg'. --config-read string Prints value for the given config key, then exits. --config-section string Optional config section when reading or writing a config key. Defaults to [settings]. (default "settings") --config-write stringToString Writes value to a config key, then exits. Expects two arguments, key and value. (default []) --cursorpos int Optional cursor position in the current file. --disable-offline Disables offline time logging instead of queuing logged time. --entity string Absolute path to file for the heartbeat. Can also be a url, domain or app when --entity-type is not file. --entity-type string Entity type for this heartbeat. Can be "file", "domain" or "app". Defaults to "file". --exclude strings Filename patterns to exclude from logging. POSIX regex syntax. Can be used more than once. --exclude-unknown-project When set, any activity where the project cannot be detected will be ignored. --extra-heartbeats Reads extra heartbeats from STDIN as a JSON array until EOF. --help help for wakatime-cli --hide-branch-names string Obfuscate branch names. Will not send revision control branch names to api. --hide-file-names string Obfuscate filenames. Will not send file names to api. --hide-project-names string Obfuscate project names. When a project folder is detected instead of using the folder name as the project, a .wakatime-project file is created with a random project name. --hostname string Optional name of local machine. Defaults to local machine name read from system. --include strings Filename patterns to log. When used in combination with --exclude, files matching include will still be logged. POSIX regex syntax. Can be used more than once. --include-only-with-project-file Disables tracking folders unless they contain a .wakatime-project file. Defaults to false. --internal-config string Optional internal config file. Defaults to '~/.wakatime-internal.cfg'. --key string Your wakatime api key; uses api_key from ~/.wakatime.cfg by default. --language string Optional language name. If valid, takes priority over auto-detected language. --lineno int Optional line number. This is the current line being edited. --lines-in-file int Optional lines in the file. Normally, this is detected automatically but can be provided manually for performance, accuracy, or when using --local-file. --local-file string Absolute path to local file for the heartbeat. When --entity is a remote file, this local file will be used for stats and just the value of --entity is sent with the heartbeat. --log-file string Optional log file. Defaults to '~/.wakatime/wakatime.log'. --log-to-stdout If enabled, logs will go to stdout. Will overwrite logfile configs. --no-ssl-verify Disables SSL certificate verification for HTTPS requests. By default, SSL certificates are verified. --offline-count Prints the number of heartbeats in the offline db, then exits. --plugin string Optional text editor plugin name and version for User-Agent header. --project string Override auto-detected project. Use --alternate-project to supply a fallback project if one can't be auto-detected. --proxy string Optional proxy configuration. Supports HTTPS SOCKS and NTLM proxies. For example: 'https://user:pass@host:port' or 'socks5://user:pass@host:port' or 'domain\user:pass' --ssl-certs-file string Override the bundled CA certs file. By default, uses system ca certs. --sync-offline-activity string Amount of offline activity to sync from your local ~/.wakatime.bdb bolt file to your WakaTime Dashboard before exiting. Can be "none" or a positive integer. Defaults to 1000, meaning after sending a heartbeat while online, all queued offline heartbeats are sent to WakaTime API, up to a limit of 1000. Can be used without --entity to only sync offline activity without generating new heartbeats. (default "1000") --time float Optional floating-point unix epoch timestamp. Uses current time by default. --timeout int Number of seconds to wait when sending heartbeats to api. Defaults to 120 seconds. (default 120) --today Prints dashboard time for Today, then exits. --today-goal string Prints time for the given goal id Today, then exits Visit wakatime.com/api/v1/users/current/goals to find your goal id. --today-hide-categories When optionally included with --today, causes output to show total code time today without categories. --verbose Turns on debug messages in log file. --version Prints the wakatime-cli version number, then exits. --write When set, tells api this heartbeat was triggered from writing to a file.

Some examples of executing wakatime-cli:

Tracking Last Heartbeat

After sending the currently focused file to wakatime-cli, your plugin should update the lastSentTime and lastSentFile global variables with the current time and currently focused file. This way they will be ready the next time we are handling an event.

Debugging

Some resources you can use while debugging your plugin.

Confirming Heartbeat Received

To confirm your heartbeats are being received, check the plugin status page or the User Agents API endpoint.

If your plugin is working, you should see it at the top of the list. Make sure the editor, version, and os attributes are detected correctly.

Logging

Logging errors and info messages from your plugin is a good way to debug problems. Each editor/IDE running your plugin usually provides a log for appending messages. You should setup logging to write there from your plugin and watch it while debugging.

wakatime-cli has it's own log file located at $HOME/.wakatime/wakatime.log. To turn on verbose logging, pass the --verbose argument to wakatime-cli. With the --verbose flag, wakatime-cli will write DEBUG messages to the $HOME/.wakatime/wakatime.log file.

Releasing

After your plugin is sending heartbeats, publish it to the world so others can use it too.

Versioning

Keep track of your plugin's version number with Semantic Versioning.

Send your plugin's version number and name to wakatime-cli with the --plugin argument.

Distribution

Put it on GitHub, send us your repo, then we’ll add it to the Community list.

Even better, transfer the repo to username alanhamlett and we’ll list it as an official plugin on the WakaTime home page and the official plugins list at https://wakatime.com/plugins. You’ll be given GitHub Maintain access on the repo after transferring.

Finally, consider adding a Buy Me a Coffee or Patreon badge to your repo’s README.md before transferring the repo.