Skip to content

cerebroapp/create-cerebro-plugin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

84 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Create Cerebro Plugin

Fastest way to create Cerebro plugins.

Quick Overview

Before start make sure you have installed yarn package manager. Follow installation instructions if you don't.

yarn create cerebro-plugin my-plugin
cd ./my-plugin
yarn start

Plugins

A Cerebro plugin is just a javascript module. All you need is to write a function, that takes one object and call a function from arguments with your results.

You can create your plugin using create-cerebro-plugin so you can focus on code of your plugin, not on tools and configuration around it.

Prerequisites

Install and manage custom plugins

Sometimes you need to manually install a plugin (maybe you have published it to npm but you dind't added the keywords to the package.json so it is not available in Cerebro). If you want to test this plugin, you can install it manually:

  1. Open a terminal in the configuration directory of Cerebro

  2. Go to the plugins directory

    cd ./plugins
  3. Install the plugin

    npm install --save name-of-plugin
  4. Restart Cerebro

Plugin structure

This is a minimum source code of your plugin:

export const fn = (scope) => console.log(scope.term)

You can open the developer tools by pressing ctrl+shift+i(for the main window) and ctrl+shift+b(for the background). Developer mode should be enabled from the settings

This plugin will write to console all changes in your search field of Cerebro app. So, fn key is a heart of your plugin: this function receives scope object and you can send results back to Cerebro. Scope object is:

  • termString, entered by Cerebro user;
  • displayFunction(result: Object | Array<object>), display your result
  • updateFunction(id: String, result: Object), update your previously displayed result. This action updates only passed fields, so if you displayed result {id: 1, title: 'Result'} and call update(1, {subtitle: 'Subtitle'}), you will have merged result: {id: 1, title: 'Result', subtitle: 'Subtitle'};
  • hideFunction(id: String), hide result from results list by id. You can use it to remove temporar results, like "loading..." placeholder;
  • actions – object with main actions, provided for cerebro plugins:
    • openFunction(path: String), open external URL in browser or open local file;
    • revealFunction(path: String), reveal file in finder;
    • copyToClipboardFunction(text: String), copy text to clipboard;
    • replaceTermFunction(text: String), replace text in main Cerebro input;
    • hideWindowFunction(), hide main Cerebro window.
  • settings - Object, contains user provided values of all specified settings keys;

Let's show something in results list:

export const fn = (scope) => {
  scope.display({
    title: 'It works!',
    subtitle: `You entered ${scope.term}`
  })
}

scope.display accepts one result object or array of result objects. Result object is:

Basic fields

title

Type: String

Title of your result;

subtitle

Type: String

Subtitle of your result;

icon

Type: String

Icon, that is shown near your result. It can be absolute URL to external image, absolute path to local image or base64-encoded data-uri.

For local icons you can use path to some .app file, i.e. /Applications/Calculator.app will render default icon for Calculator application.

Advanced fields

id

Type: String Use this field when you need to update your result dynamically. Check id example

term

Type: String

Autocomplete for your result. So, user can update search term using tab button;

clipboard

Type: String

Text, that will be copied to clipboard using cmd+c, when your result is focused;

getPreview

Type: Function

Arguments: no

Function that returns preview for your result. Preview can be an html string or React component;

onSelect

Type: Function. Arguments: event: Event

Action, that should be executed when user selects your result. I.e, to open provided url in default browser:

onSelect: (event) => actions.open(`http://www.cerebroapp.com`),

If you don't want to close main window after your action, you should call event.preventDefault() in your action.

onKeyDown

Type: Function

Arguments: event: Event

Handle keyboard events when your result is focused, so you can do custom actions, i.e. reveal file in finder by cmd+r (or ctrl+r on windows and linux):

onKeyDown: (event) => {
  if ((event.metaKey || event.ctrlKey) && event.keyCode === 82) {
    actions.reveal(path);
    event.preventDefault();
  }
}

You can also prevent default action by event.preventDefault().

Advanced plugin fields

Along with fn, your module could have more keys:

keyword

Type: String

This field is used for autocomplete. You can prefix your plugin usage by this keyword. Checkout emoji example

name

Type: String

This field is also used for autocomplete and shown as title in results list. Checkout emoji example

initialize

Type: Function Arguments: no

Use this function, when you need to prepare some data for your plugin on start. If you need to do some long-running processes, check initializeAsync

Check initialize example

initializeAsync

Type: Function

Arguments: callback: Function(message: Object) – callback to send data back to main process.

Use this function when you need to execute some long-running initializer process. I.e. in contacts plugin we are fetching all contacts using osx-specific libraries using nodobjc module.

This function will be executed in another process and you can receive results using onMessage function.

Check initializeAsync and onMessage example

onMessage

Type: Function Arguments: message: Object – object that you sent from initializeAsync

Use this function to receive data back from your initializeAsync function.

Check initializeAsync and onMessage example

settings

Type: Object

This object is used to specify settings that a plugin user can change. Each setting should include a description and a type. Other keys include:

  • label - String, object key for the setting. also used to access it;
  • description - String, description of the setting;
  • type - String, used to decide element for rendering a setting:
    • string
    • number
    • bool
    • option
  • defaultValue - Any, default value for the setting;
  • options - Array, all possible options that can be selected by the user. applicable only for option;
  • multi - Bool, allows user to select more than one option for option settings. applicable only for option;
  • createable - Bool, allows user created options. applicable only for option;

Check settings example

Take a look at React Select for more details on how the option type works.

Available env variables

The following variables are available in the process.env object:

  • CEREBRO_VERSION – Version of Cerebro
  • CEREBRO_DATA_PATH – Path to Cerebro data directory

Styles for your plugin preview

Currently if you want to reuse main app styles, you can use CSS variables from main themes (light, dark)

It is better to reuse css variables so custom themes can affect not only main app styles, but your plugin too.

Example (reuse main border styles):

.item {
  border-bottom: var(--main-border);
}

Reusable components

Share

When your plugin is ready, you can share it with all Cerebro users so they can find and install it using plugins command in Cerebro.

All you need is to publish your module to npm. Just run from your plugin folder:

npm publish ./

If you have any problems check out publishing packages in npm documentation

Checklist

  1. Update your repository Readme.md, add screenshot or gif;
  2. Push your plugin to open github repository – this repository is used by cerebro, at least to show Readme.md of your plugin;
  3. Make sure that you have changed package.json metadata: module name, description, author and link to github repository;
  4. Add cerebro-plugin keyword to package.json keywords section. Otherwise your plugin won't be shown in Cerebro;

Examples

You always can check out source code of existing plugins, like:

Using id

export const fn = ({display}) => {
  display({
    id: 'my-id',
    title: 'Loading'
  })
  fetchResult().then((result) => {
    display({
      id: 'my-id',
      title: `Fetched result: ${result}`
    })
  });
}

Using icon

import icon from '[path-to-icon]/icon.png';

const plugin = ({display}) => {
  display({
    icon,
    title: 'Title',
    subtitle: 'Subtitle'
  });
}

export default {
  fn: plugin,
}

Using keyword and name

const plugin = (scope) => {
  const match = scope.term.match(/^emoj\s(.+)/);
  if (match) {
    searchEmojis(match[1]).then(results => {
      scope.display(results)
    })
  };
}

export default {
  name: 'Search emojis...',
  fn: plugin,
  keyword: 'emoj'
}

Using initialize

// Some data needed for your plugin
let data;

// Fetch some data only on app initialization
const initialize = () => {
  fetchYourData().then(result => {
    data = result
  });
}

const plugin = (scope) => {
  const results = search(data, scope.term);
  scope.display(results);
}

export default {
  initialize: initialize,
  fn: plugin
}

Using initializeAsync and onMessage

let data;

// Run some long-running initialization process in background
const initialize = (cb) => {
  fetchYourData().then(cb);
  // and re-fetch this information once in 1h
  setInterval(() => {
    initialize(cb);
  }, 60 * 3600);
}

const onMessage = (results) => {
  data = results;
}

const plugin = (scope) => {
  const results = search(data, scope.term);
  scope.display(results);
}

export default {
  initializeAsync: initialize,
  onMessage: onMessage,
  fn: plugin
}

Using cerebro-tools

import { memoize, search } from 'cerebro-tools';
import preprocessJson from './preprocessJson';

// Memoize your fetched data from external API
const fetchData = memoize(() => {
  return fetch('http://api/url')
    .then(response => response.json())
    .then(preprocessJson)
});

const plugin = ({term, display}) => {
  fetchData().then(data => {
    const results = search(data, term, (el) => el.name);
    display(term);
  })
}

export default {
  fn: plugin
};

using settings

const plugin = ({ display, settings }) => {
  const icon = require('[path-to-icon]/icon.png');

  display({
    icon: settings.icon ? icon : '',
    title: `${settings.username}, you have been around for ${settings.age}`,
    subtitle: `Favorite languages: ${settings.languages.join(',')}`,
  })
}

export default {
  fn: plugin,
  settings: {
    username: { type: 'string' },
    age: { type: 'number', defaultValue: 42 },
    icon: { type: 'bool' },
    languages: {
      type: 'option',
      description: 'Your favorite programming languages'
      options: [
        { label: 'JavaScript', value: 'js' },
        { label: 'Haskell', value: 'hsk' },
        { label: 'Rust', value: 'rs' }
      ],
      multi: true,
      createable: true,
    }
  }
}