Shortcut to Everything

An open-source kit to optimize your developer workflow

Download Script Kit:

microsoft Windows
linux Linux (Community)
John Lindquist

Script Kit is made for developers who understand the inherent risks of running scripts on their computer. If you're uncomfortable with any aspect of writing, running, or sharing scripts, please ask for help!

Script Kit doesn't collect any personal information.


What is Script Kit?

How often do you avoid scripting something because it takes too much effort?

Script Kit makes it easy to create and run scripts that solve your daily problems. Create a new script from the prompt then your script opens in the editor of your choice. Write a few lines of JavaScript. Then run the script from the prompt.

Simply put, Script Kit helps you script away the friction of your day.

Key Features

  • ▪︎Write your scripts in TypeScript with our SDK. Zero config.
  • ▪︎Launch the prompt from anywhere as part of your flow
  • ▪︎Add keyboard shortcuts with comments://Shortcut: opt a
  • ▪︎Prompt for input with:await arg("First name")
  • ▪︎Prompt for environment vars:await env("GITHUB_TOKEN")
  • ▪︎Watches your scripts and prompts to install npm libraries:import express from "express"
  • ▪︎Share scripts directly from the prompt
  • ▪︎Launch scripts from a Stream Deck button
  • ▪︎Scripts behave the same in your terminal

Learn more

Script Kit includes a built-in tutorial and myriad examples are available on our discussions pages:

Join the Script Kit community and stay up-to-date on the latest updates!

No spam. We respect your privacy. Unsubscribe at any time.

What the community is saying

I forgot that a lot of people don't know what @scriptkitapp is. You're missing out! I use it to easily open projects in VSCode, start a zoom meeting and put the link in my clipboard, download Twitter images, upload images to cloudinary, and so much more!

So, @scriptkitapp is AMAZING.

Just spent a very happy morning figuring out a script where it takes my latest recording from OBS and trims the silence from the start and end with ffmpeg.

Now, it's just a command palette action away.

I just learned about Script Kit, and my mind is racing with all the stuff I could do with it.

@scriptkitapp is dope!

Took me less then a minute to create a command palette script for killing a port 🤩

Featured Scripts

// Menu: New Post
// Description: Create a new blog post
// Author: Kent C. Dodds
// Shortcut: command option control p
// Twitter: @kentcdodds
const dateFns = await npm('date-fns')
const prettier = await npm('prettier')
const YAML = await npm('yaml')
const slugify = await npm('@sindresorhus/slugify')
const {format: formatDate} = await npm('date-fns')
const makeMetascraper = await npm('metascraper')
const {$filter, toRule} = await npm('@metascraper/helpers')
const unsplashTitleToAlt = toRule(str => str.replace(/ photo – .*$/, ''))
const unsplashOGTitleToAuthor = toRule(str =>
str.replace(/Photo by (.*?) on Unsplash/, '$1'),
)
const unsplashImageToPhotoId = toRule(str =>
new URL(str).pathname.replace('/', ''),
)
const metascraper = makeMetascraper([
{
unsplashPhotoId: [
unsplashImageToPhotoId($ =>
$('meta[property="og:image"]').attr('content'),
),
],
},
{
author: [
unsplashOGTitleToAuthor($ =>
$('meta[property="og:title"]').attr('content'),
),
],
},
{alt: [unsplashTitleToAlt($ => $('title').text())]},
])
async function getMetadata(url) {
const html = await fetch(url).then(res => res.text())
return metascraper({html, url})
}
const blogDir = await env(
'KCD_BLOG_CONTENT_DIR',
`What's the path to the blog content directory on this machine?`,
)
const title = await arg({
placeholder: `What's the title of this post?`,
hint: 'Title',
ignoreBlur: true,
})
const description = await arg({
placeholder: `What's the description of this post?`,
hint: 'Description',
input: 'TODO: add a description',
ignoreBlur: true,
})
const categories = (
await arg({
placeholder: `What are the categories of this post?`,
hint: 'Categories (comma separated)',
ignoreBlur: true,
})
)
.split(',')
.map(c => c.trim())
.filter(Boolean)
const keywords = (
await arg({
placeholder: `What are the keywords of this post?`,
hint: 'Keywords (comma separated)',
ignoreBlur: true,
})
)
.split(',')
.map(c => c.trim())
.filter(Boolean)
const filename = slugify(title, {decamelize: false})
await exec(`open https://unsplash.com/s/photos/${filename}`)
const unsplashPhotoInput = await arg({
placeholder: `What's the unsplash photo?`,
hint: 'Unsplash Photo',
ignoreBlur: true,
})
const unsplashPhotoUrl = unsplashPhotoInput.startsWith('http')
? unsplashPhotoInput
: `https://unsplash.com/photos/${unsplashPhotoInput}`
const metadata = await getMetadata(unsplashPhotoUrl)
const frontmatter = YAML.stringify({
title,
date: dateFns.format(new Date(), 'yyyy-MM-dd'),
description,
categories,
meta: {keywords},
bannerCloudinaryId: `unsplash/${metadata.unsplashPhotoId}`,
bannerAlt: metadata.alt,
bannerCredit: `Photo by [${metadata.author}](${unsplashPhotoUrl})`,
})
const md = `---
${frontmatter}
---
Be excellent to each other.
`
// prettify the markdown
const prettyMd = await prettier.format(md, {
parser: 'markdown',
arrowParens: 'avoid',
bracketSpacing: false,
embeddedLanguageFormatting: 'auto',
htmlWhitespaceSensitivity: 'css',
insertPragma: false,
jsxBracketSameLine: false,
jsxSingleQuote: false,
printWidth: 80,
proseWrap: 'always',
quoteProps: 'as-needed',
requirePragma: false,
semi: false,
singleQuote: true,
tabWidth: 2,
trailingComma: 'all',
useTabs: false,
vueIndentScriptAndStyle: false,
})
const newFile = path.join(blogDir, `${filename}.mdx`)
await writeFile(newFile, prettyMd)
await edit(newFile)
// Menu: Giphy
// Description: Search giphy. Paste link.
// Author: John Lindquist
// Twitter: @johnlindquist
let download = await npm("image-downloader")
let queryString = await npm("query-string")
let GIPHY_API_KEY = await env("GIPHY_API_KEY", {
hint: md(
`Get a [Giphy API Key](https://developers.giphy.com/dashboard/)`
),
ignoreBlur: true,
secret: true,
})
let search = q =>
`https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${q}&limit=10&offset=0&rating=g&lang=en`
let { input, url } = await arg(
"Search giphy:",
async input => {
if (!input) return []
let query = search(input)
let { data } = await get(query)
return data.data.map(gif => {
return {
name: gif.title.trim() || gif.slug,
value: {
input,
url: gif.images.original.url,
},
preview: `<img src="${gif.images.downsized.url}" alt="">`,
}
})
}
)
let formattedLink = await arg("Format to paste", [
{
name: "URL Only",
value: url,
},
{
name: "Markdown Image Link",
value: `![${input}](${url})`,
},
{
name: "HTML <img>",
value: `<img src="${url}" alt="${input}">`,
},
])
setSelectedText(formattedLink)
// Menu: Resize an Image
// Description: Select an image in Finder. Type option + i to resize it.
// Author: John Lindquist
// Twitter: @johnlindquist
// Shortcut: opt i
let sharp = await npm("sharp")
let imagePath = await getSelectedFile()
let width = Number(await arg("Enter width:"))
let metadata = await sharp(imagePath).metadata()
let newHeight = Math.floor(
metadata.height * (width / metadata.width)
)
let lastDot = /.(?!.*\.)/
let resizedImageName = imagePath.replace(
lastDot,
`-${width}.`
)
await sharp(imagePath)
.resize(width, newHeight)
.toFile(resizedImageName)