Fastest way to create Cerebro plugins.
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
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.
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:
-
Open a terminal in the configuration directory of Cerebro
-
Go to the plugins directory
cd ./plugins
-
Install the plugin
npm install --save name-of-plugin
-
Restart Cerebro
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) andctrl+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:
term
–String
, entered by Cerebro user;display
–Function(result: Object | Array<object>)
, display your resultupdate
–Function(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 callupdate(1, {subtitle: 'Subtitle'})
, you will have merged result:{id: 1, title: 'Result', subtitle: 'Subtitle'}
;hide
–Function(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:open
–Function(path: String)
, open external URL in browser or open local file;reveal
–Function(path: String)
, reveal file in finder;copyToClipboard
–Function(text: String)
, copy text to clipboard;replaceTerm
–Function(text: String)
, replace text in main Cerebro input;hideWindow
–Function()
, 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:
Type: String
Title of your result;
Type: String
Subtitle of your result;
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.
Type: String
Use this field when you need to update your result dynamically. Check id
example
Type: String
Autocomplete for your result. So, user can update search term using tab button;
Type: String
Text, that will be copied to clipboard using cmd+c, when your result is focused;
Type: Function
Arguments: no
Function that returns preview for your result. Preview can be an html string or React component;
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.
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()
.
Along with fn
, your module could have more keys:
Type: String
This field is used for autocomplete. You can prefix your plugin usage by this keyword. Checkout emoji example
Type: String
This field is also used for autocomplete and shown as title in results list. Checkout emoji example
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
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
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
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 foroption
;multi
-Bool
, allows user to select more than one option foroption
settings. applicable only foroption
;createable
-Bool
, allows user created options. applicable only foroption
;
Check settings
example
Take a look at React Select for more details on how the option
type works.
The following variables are available in the process.env
object:
CEREBRO_VERSION
– Version of CerebroCEREBRO_DATA_PATH
– Path to Cerebro data directory
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);
}
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
- Update your repository
Readme.md
, add screenshot or gif; - Push your plugin to open github repository – this repository is used by cerebro, at least to show
Readme.md
of your plugin; - Make sure that you have changed package.json metadata: module name, description, author and link to github repository;
- Add
cerebro-plugin
keyword to package.json keywords section. Otherwise your plugin won't be shown in Cerebro;
You always can check out source code of existing plugins, like:
export const fn = ({display}) => {
display({
id: 'my-id',
title: 'Loading'
})
fetchResult().then((result) => {
display({
id: 'my-id',
title: `Fetched result: ${result}`
})
});
}
import icon from '[path-to-icon]/icon.png';
const plugin = ({display}) => {
display({
icon,
title: 'Title',
subtitle: 'Subtitle'
});
}
export default {
fn: plugin,
}
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'
}
// 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
}
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
}
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
};
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,
}
}
}