Project Name | Stars | Downloads | Repos Using This | Packages Using This | Most Recent Commit | Total Releases | Latest Release | Open Issues | License | Language |
---|---|---|---|---|---|---|---|---|---|---|
Slack Term | 6,251 | 1 | 3 months ago | 13 | January 08, 2020 | 61 | mit | Go | ||
Slack client for your terminal | ||||||||||
Sclack | 2,422 | 10 months ago | 46 | gpl-3.0 | Python | |||||
The best CLI client for Slack, because everything is terrible! | ||||||||||
Gomplate | 2,281 | 49 | a day ago | 83 | March 11, 2023 | 32 | mit | Go | ||
A flexible commandline tool for template rendering. Supports lots of local and remote datasources. | ||||||||||
Slackcat | 1,204 | a month ago | 2 | May 22, 2021 | 12 | mit | Go | |||
CLI utility to post files and command output to slack | ||||||||||
Multi | 1,156 | a month ago | 23 | gpl-3.0 | Swift | |||||
Create a custom, lightweight macOS app from a group of websites | ||||||||||
Command | 1,077 | 6 years ago | 19 | mit | JavaScript | |||||
:black_nib: Making the web better with Slack-like slash commands. | ||||||||||
Slack Cli | 978 | 8 months ago | 38 | Shell | ||||||
:neckbeard: Powerful Slack CLI via pure bash. Rich messaging, uploads, posts, piping, oh my! | ||||||||||
Cog | 914 | 2 years ago | 62 | other | Elixir | |||||
Bringing the power of the command line to chat | ||||||||||
Slacker | 787 | 1 | 7 | 2 months ago | 3 | July 16, 2022 | mit | Go | ||
Slack Bot Framework | ||||||||||
Cli | 729 | 3 days ago | 33 | May 20, 2022 | 4 | other | Go | |||
`kubectl` plugin for KubeDB |
Create a custom, lightweight macOS app from a group of websites, complete with:
Watch me create a Slack clone from scratch in 30 seconds (high res video):
I've also written a few blog posts that discuss some of the decisions behind Multi:
If you enjoy using Multi, please consider showing your appreciation with a donation!
The easiest method is to use Homebrew:
brew install --cask multi
Alternatively, you can manually download and run the latest .dmg
from Releases.
Multi apps store their configuration in a single JSON file.
If your app is named Test
, then you'll find that file at /Applications/Multi/Test.app/Contents/Resources/config.json
.
The JSON configuration uses the following top-level fields:
Field Name | Type | Description |
---|---|---|
tabs |
Array | Titles and URLs of tabs for this app (Required) |
windowed |
Boolean | Start this app with each tab in its own window |
alwaysNotify |
Boolean | Show macOS notifications even if this app is currently focused |
alwaysOnTop |
Boolean | Position this app's window on top of all others |
terminateWithLastWindow |
Boolean | Determine if this app closes once all tabs/windows are closed |
openNewWindowsInBackground |
Boolean | Determines if browser app becomes active when opening external links |
openNewWindowsWith |
String | Override system default browser for external linksvalue is a bundle identifier like com.apple.Safari , com.google.Chrome , or com.mozilla.firefox
|
The tabs
field is an array of objects with the following fields:
Field Name | Type | Description |
---|---|---|
url |
String | Starting page for this tab (Required) |
title |
String | Name for this tab |
customJs |
Array of Strings | Custom JS URLs (see Custom JS/CSS) |
customCss |
Array of Strings | Custom CSS URLs (see Custom JS/CSS) |
customCookies |
Array of Objects | Custom cookies using HTTPCookiePropertyKey |
basicAuthUser |
String | User name credential for requests that use basic access authentication |
basicAuthPassword |
String | Password credential for requests that use basic access authentication |
userAgent |
String | Override the default WebKit user agent header |
Here's a bare minimum example to recreate the Slack demo video above:
{ "tabs": [{ "url": "https://app.slack.com/client" }] }
Here's a fancier example that uses the optional fields referenced above:
{
"tabs": [
{
"title": "Dancing",
"url": "https://rc.kofi.sexy/bathroom-floss",
"basicAuthUser": "user",
"basicAuthPassword": "password",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Safari/605.1.15"
},
{
"title": "Walking",
"url": "https://kofi.sexy/cel-shading",
"customJs": [ "file:///Users/kofi/Documents/dotfiles/main/example.js" ],
"customCss": [ "https://example.com/custom.css" ],
"customCookies": [
{
"name": "login_token_tab",
"value": "eyJoZWxsbyI6ICJ3b3JsZCJ9",
"domain": ".example.com",
"path": "/"
}
]
}
],
"windowed": true,
"alwaysNotify": true,
"alwaysOnTop": true,
"terminateWithLastWindow": true,
"openNewWindowsInBackground": true,
"openNewWindowsWith": "com.apple.Safari"
}
If your configuration file fails to decode, you can use the settings window to fix the issues.
Optional fields will always default to "empty" values (i.e. false
, ""
, []
).
create-mac-app
You can create and update Multi apps entirely from the command-line with the included script.
In fact, the Multi configuration UI just runs this script under-the-hood!
The create-mac-app
script takes its options as environment variables.
For instance, here's how you'd create a bare-minimum app named Test
:
MULTI_APP_NAME='Test' /Applications/Multi.app/Contents/Resources/create-mac-app
When you open Test
, you'll be greeted with the preferences window, where you can finish configuring your app.
If you'd like to configure your app entirely from the command-line, you can set any of the following variables:
MULTI_ICON_PATH |
PNG or ICNS path to icon image |
MULTI_JSON_CONFIG |
See JSON configuration |
MULTI_OVERWRITE |
Set to 1 to replace an existing Multi app with the same name |
Multi lets you customize any site by injecting JavaScript and CSS on every page in your app. Each custom JS/CSS file is specified with a URL, which gives you a few options for how you want to manage your customizations:
https://raw.githubusercontent.com/kofigumbs/dotfiles/master/example.js
file:///Users/kofi/workspace/dotfiles/example.js
data:,console.log%28%27Hello%2C%20from%20Multi%21%27%29%3B%0A
Custom JS/CSS is one of the most important parts of Multi. It lets the main project stay small and focused, while letting you extend it with new features that fit your use case. If you have a neat JS/CSS snippet, you'd like to share, please open an Issue or Pull Request! Here are a few that have come up before:
Google seems to be doing some trickery here. Instead of allowing the browser to handle the links, they use JS to open a blank popup window, then dynamically set the URL to google.com/url?q=REAL_URL_HERE. Presumably all of this is so that they can track you for a few moments on your way out of their app. Custom JS solution:
window.addEventListener("DOMContentLoaded", () => {
const listener = e => e.stopPropagation();
const query = () => document.querySelectorAll("a[target=_blank]").forEach(a => {
a.removeEventListener("click", listener);
a.addEventListener("click", listener, true);
});
setInterval(query, 400); // wait time between DOM queries, in milliseconds
});
Multi doesn't include any search functionality (Cmd-F). Custom JS solution:
window.addEventListener("DOMContentLoaded", () => {
const highlightResults = (text, color) => {
document.designMode = "on"; // https://stackoverflow.com/a/5887719
var selection = window.getSelection();
selection.collapse(document.body, 0);
while (window.find(text)) {
document.execCommand("HiliteColor", false, color);
selection.collapseToEnd();
}
document.designMode = "off";
};
let mostRecentSearchText = "";
const search = text => {
highlightResults(mostRecentSearchText, "transparent");
highlightResults(text, "rgb(255 255 1 / 50%)");
mostRecentSearchText = text;
};
const input = document.createElement("input");
input.placeholder = "Search...";
input.style.padding = "10px 15px";
input.style.fontSize = "15px";
input.style.borderRadius = "3px";
input.style.border = "solid 1px lightgray";
const form = document.createElement("form");
form.style.display = "none";
form.style.position = "fixed";
form.style.top = "15px";
form.style.right = "15px";
form.style.zIndex = "2147483647"; // https://stackoverflow.com/a/856569
form.addEventListener("submit", e => {
e.preventDefault();
search(input.value);
});
const close = document.createElement("a");
close.innerText = "";
close.href = "javascript:void(0)";
close.style.fontSize = "30px";
close.style.padding = "15px";
close.style.textDecoration = "none";
close.addEventListener("click", e => {
e.preventDefault();
search("");
form.style.display = "none";
});
form.appendChild(input);
form.appendChild(close);
document.body.appendChild(form);
document.addEventListener("keydown", event => {
if (event.metaKey && event.key === "f") {
event.preventDefault();
form.style.display = "block";
input.focus();
}
});
});
Say you have a URL outside of Multi (maybe in an email), and you want to open it in Multi. Custom JS solution:
document.addEventListener("dragover", e => e.preventDefault());
Multi doesn't include any hover-to-preview-link-target functionality. Custom CSS solution:
a:hover::after {
content: attr(href);
position: fixed;
left: 4px;
bottom: 4px;
padding: 4px;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont;
font-weight: normal;
color: black;
background: ghostwhite;
border: solid 1px black;
border-radius: 1px;
}