├── src ├── types │ ├── kitapp.d.mjs │ └── index.d.ts ├── cli │ ├── background.ts │ ├── sdk.ts │ ├── open-kenv.ts │ ├── open-kit.ts │ ├── browse.ts │ ├── caffeinate.ts │ ├── env.ts │ ├── faq.ts │ ├── tutorials.ts │ ├── credits.ts │ ├── open.ts │ ├── browse-examples.ts │ ├── change-editor.ts │ ├── authenticate.ts │ ├── goto-docs.ts │ ├── goto-guide.ts │ ├── get-help.ts │ ├── toggle-tray.ts │ ├── notify.ts │ ├── open-log.ts │ ├── update.ts │ ├── main-log.ts │ ├── manual-npm.ts │ ├── quit.ts │ ├── refresh-scripts-db.ts │ ├── edit-file.ts │ ├── clear.ts │ ├── toggle-auto-update.ts │ ├── installs.ts │ ├── build-widget.ts │ ├── postinstall.ts │ ├── open-command-log.ts │ ├── switch-to-js.ts │ ├── sync-path-instructions.ts │ ├── info.ts │ ├── issue.ts │ ├── pull-script.ts │ ├── push-script.ts │ ├── edit.ts │ ├── tail-log.ts │ ├── disable-telemetry.ts │ ├── disable-auto-update.ts │ ├── open-app.ts │ ├── clear-recent.ts │ ├── error-prompt.ts │ ├── prefs.ts │ ├── kit-clear-prompt.ts │ ├── feedback.ts │ ├── reveal.ts │ ├── remove-from-recent.ts │ ├── copy-path.ts │ ├── kenv-switch.ts │ ├── open-script-log.ts │ ├── new-from-clipboard.ts │ ├── reveal-script.ts │ ├── open-script-database.ts │ ├── create-bin.ts │ ├── select-mic.ts │ ├── select-webcam.ts │ ├── share-copy.ts │ ├── kenv-term.ts │ ├── tutorial.ts │ ├── sync-path.ts │ ├── sync-path-node.ts │ ├── exists.ts │ ├── settings.ts │ ├── more-info.ts │ ├── open-at-login.ts │ ├── share-script-as-kit-link.ts │ ├── join.ts │ ├── new-from-template.ts │ ├── npm.ts │ ├── run.ts │ ├── download-md.ts │ ├── remove.ts │ ├── view-docs.ts │ ├── remove-env-var.ts │ ├── switch-to-ts.ts │ ├── share-script.ts │ ├── paste-as-markdown.ts │ ├── kenv-change-dir.ts │ ├── share-script-as-link.ts │ ├── change-main-shortcut.ts │ ├── new-scriptlet.ts │ ├── kenv.test.js │ ├── manage-npm.ts │ ├── editor-history.ts │ ├── new-quick.ts │ ├── clear-script-database.ts │ ├── update-kit-package.ts │ ├── update-package.ts │ ├── log-widget.ts │ ├── toggle-background.ts │ ├── uninstall.ts │ ├── lib │ │ └── install.ts │ ├── kenv-distrust.ts │ ├── search-docs.ts │ ├── kenv-visit.ts │ ├── system-events.ts │ ├── kenv-pull.ts │ ├── new-from-url.ts │ ├── schedule.ts │ ├── share-script-as-markdown.ts │ ├── edit-doc.ts │ ├── add-kit-to-profile.ts │ ├── stream-deck.ts │ ├── share-script-as-discussion.ts │ ├── add-kenv-to-profile.ts │ ├── rename.ts │ ├── share.ts │ ├── create-all-bins-no-trash.ts │ └── kenv-push.ts ├── setup │ ├── degit-kenv.ts │ ├── ensure-snippets.ts │ ├── clone-docs.ts │ ├── ensure-scriptlets.ts │ ├── setup.ts │ ├── chmod-helpers.ts │ ├── clone-sponsors.ts │ ├── switch-windows-kit-to-bat.ts │ ├── kitblitz.ts │ ├── create-env.ts │ ├── update-examples.ts │ ├── cache-grouped-scripts.ts │ ├── downloads.ts │ ├── ensure-scriptlets.test.ts │ └── clone-examples.ts ├── help │ ├── follow.ts │ ├── reveal-kenv.ts │ ├── reveal-kit.ts │ ├── tail-log.ts │ ├── reveal-kit-log.ts │ ├── install-vscode-extension.ts │ ├── authorized-info.ts │ ├── download-docs.ts │ ├── info-full-disk-access.ts │ └── join.ts ├── config │ ├── set-login.ts │ ├── toggle-sponsor.ts │ ├── set-accessible.ts │ └── set-default-display.ts ├── main │ ├── help.ts │ ├── dev.ts │ ├── rhyme.ts │ ├── define.ts │ ├── emoji.ts │ ├── sign-in.ts │ ├── templates.ts │ ├── accessibility.ts │ ├── showandtell.ts │ ├── term.ts │ ├── docs.ts │ ├── kit-windows.ts │ ├── tips.ts │ ├── kit.ts │ ├── api.ts │ ├── guide.ts │ ├── suggest.ts │ ├── sticky.ts │ ├── open-with.ts │ ├── system-commands.ts │ ├── sponsor.ts │ └── edit.ts ├── api │ ├── packages │ │ ├── tmpPromise.ts │ │ ├── open.ts │ │ ├── zx.ts │ │ ├── clipboardy.ts │ │ ├── shelljs.ts │ │ └── trash.ts │ ├── recent.ts │ ├── lib.ts │ └── npm.test.ts ├── globals │ ├── globby.ts │ ├── crypto.ts │ ├── download.ts │ ├── handlebars.ts │ ├── replace-in-file.ts │ ├── chalk.ts │ ├── child_process.ts │ ├── axios.ts │ ├── stream.ts │ ├── process.ts │ ├── execa.test.ts │ ├── index.ts │ ├── path.ts │ ├── execa.ts │ └── custom.ts ├── handler │ ├── comma-handler.ts │ ├── equals-handler.ts │ ├── question-handler.ts │ ├── quote-handler.ts │ ├── tilde-handler.ts │ ├── zero-handler.ts │ ├── backtick-handler.ts │ ├── colon-handler.ts │ ├── greaterthan-handler.ts │ ├── number-handler.ts │ ├── period-handler.ts │ ├── pipe-handler.ts │ ├── slash-handler.ts │ ├── backslash-handler.ts │ ├── doublequote-handler.ts │ ├── leftbracket-handler.ts │ ├── minus-handler.ts │ ├── semicolon-handler.ts │ └── lessthan-handler.ts ├── debug │ └── test-notification.ts ├── app │ ├── toggle-watcher.ts │ └── paste-snippet.ts ├── permissions │ ├── snippets.ts │ └── clipboard-history.ts ├── workers │ ├── kit-worker.ts │ ├── index.ts │ └── create-bin-worker.ts ├── lib │ ├── utils.ts │ ├── text.ts │ └── audio.ts ├── hot │ └── download-hot.ts ├── emoji │ └── download-emoji.ts ├── platform │ └── stackblitz.ts ├── pro │ ├── login.ts │ ├── sponsor-check.ts │ └── sponsor.ts ├── run │ ├── stackblitz.ts │ ├── app.ts │ └── github-workflow.ts ├── core │ ├── color.ts │ ├── test-utils.ts │ ├── defaults.ts │ ├── shebang.ts │ ├── scriptlet.utils.ts │ ├── is.ts │ ├── constants.ts │ ├── resolvers.ts │ └── snippets.test.ts ├── scripts │ └── init.ts ├── ci │ └── create-tag.ts └── index.ts ├── root ├── node │ └── .gitignore ├── templates │ ├── widget │ │ └── widget.svelte │ ├── scripts │ │ ├── default.js │ │ ├── default.ts │ │ ├── snippet.js │ │ ├── snippet.ts │ │ ├── clipboard-template.js │ │ ├── clipboard-template.ts │ │ ├── template.js │ │ └── template.ts │ ├── env │ │ └── template.env │ ├── config │ │ └── tsconfig.json │ └── bin │ │ ├── cmd │ │ ├── template │ │ ├── terminal │ │ └── stackblitz ├── db │ └── .gitignore ├── .npmrc ├── tmp │ ├── clipboard │ │ └── .gitignore │ ├── downloads │ │ └── .gitignore │ ├── scripts │ │ └── .gitignore │ └── .gitignore ├── images │ ├── icon.png │ ├── kent.jpg │ ├── matt.jpg │ └── stream-deck.png ├── bin │ ├── k │ ├── sk │ └── kit ├── override │ ├── stackblitz │ │ └── kit │ └── code │ │ └── python ├── sk ├── icons │ ├── file.svg │ └── folder.svg ├── logos │ └── vercel │ │ ├── symbol-light.svg │ │ └── symbol-dark.svg ├── kar ├── themes │ ├── nord.css │ ├── oceanic-next.css │ ├── ayu-dark.css │ ├── gruvbox.css │ ├── monokai.css │ ├── ayu-light.css │ ├── ayu-mirage.css │ ├── city-lights.css │ ├── github-dark.css │ ├── monokai-pro.css │ ├── night-owl.css │ ├── nord-frost.css │ ├── nord-light.css │ ├── one-light.css │ ├── palenight.css │ ├── tokyo-night.css │ ├── cobalt2-flat.css │ ├── cobalt2.css │ ├── dracula-soft.css │ ├── dracula.css │ ├── github-light.css │ ├── gruvbox-light.css │ ├── one-dark-pro.css │ ├── panda-syntax.css │ ├── synthwave-84.css │ ├── atom-light.css │ ├── catppuccin-frappe.css │ ├── catppuccin-latte.css │ ├── catppuccin-mocha.css │ ├── material-darker.css │ ├── material-ocean.css │ ├── night-owl-light.css │ ├── nord-aurora.css │ ├── solarized-dark.css │ ├── solarized-light.css │ ├── tomorrow-night.css │ ├── winter-is-coming.css │ ├── catppuccin-macchiato.css │ ├── github-dark-dimmed.css │ ├── material-lighter.css │ ├── material-palenight.css │ ├── tokyo-night-light.css │ ├── tokyo-night-storm.css │ ├── material-deep-ocean.css │ ├── night-owl-no-italics.css │ ├── synthwave-84-alpha.css │ ├── synthwave-84-no-glow.css │ ├── github-light-colorblind.css │ ├── night-owl-light-italic.css │ ├── winter-is-coming-light.css │ ├── github-dark-high-contrast.css │ ├── night-owl-light-no-italics.css │ ├── solarized-dark-high-contrast.css │ ├── script-kit-dark.css │ └── script-kit-light.css └── script ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── .npmignore ├── scripts ├── dist.js ├── script-with-export.js ├── test-post.js ├── clean.js ├── build-bundles.js └── init.js ├── .npmrc ├── .prettierrc.json ├── SIGN_IN.md ├── dtsgen.json ├── test-sdk └── ava.config.js ├── test └── ava.config.mjs ├── tsconfig-declaration.json ├── tsconfig.json ├── LICENSE └── README.md /src/types/kitapp.d.mjs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /root/node/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /root/templates/widget/widget.svelte: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/cli/background.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /root/db/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: johnlindquist 2 | -------------------------------------------------------------------------------- /root/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /root/tmp/clipboard/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /root/tmp/downloads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /root/tmp/scripts/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /src/cli/sdk.ts: -------------------------------------------------------------------------------- 1 | await edit(kitPath()) 2 | export {} 3 | -------------------------------------------------------------------------------- /root/templates/scripts/default.js: -------------------------------------------------------------------------------- 1 | import "@johnlindquist/kit" 2 | -------------------------------------------------------------------------------- /root/templates/scripts/default.ts: -------------------------------------------------------------------------------- 1 | import "@johnlindquist/kit" 2 | -------------------------------------------------------------------------------- /src/cli/open-kenv.ts: -------------------------------------------------------------------------------- 1 | await edit(kenvPath()) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/open-kit.ts: -------------------------------------------------------------------------------- 1 | await edit(kitPath()) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/browse.ts: -------------------------------------------------------------------------------- 1 | browse(`https://scriptkit.com`) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/caffeinate.ts: -------------------------------------------------------------------------------- 1 | exec(`caffeinate -u`) 2 | hide() 3 | 4 | export {} 5 | -------------------------------------------------------------------------------- /src/cli/env.ts: -------------------------------------------------------------------------------- 1 | await edit(kenvPath(".env"), env.KENV) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/faq.ts: -------------------------------------------------------------------------------- 1 | open(`https://github.com/johnlindquist/kit/discussions`) 2 | -------------------------------------------------------------------------------- /src/setup/degit-kenv.ts: -------------------------------------------------------------------------------- 1 | await trash(kenvPath(".git")) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /root/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !downloads 4 | !clipboard 5 | !scripts -------------------------------------------------------------------------------- /src/cli/tutorials.ts: -------------------------------------------------------------------------------- 1 | open("https://scriptkit.com/tutorials") 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node 2 | node_modules 3 | **/*.test.js 4 | **/*.test.ts 5 | test 6 | test-sdk -------------------------------------------------------------------------------- /src/cli/credits.ts: -------------------------------------------------------------------------------- 1 | browse(`https://twitter.com/johnlindquist`) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/open.ts: -------------------------------------------------------------------------------- 1 | // Name: Open 2 | 3 | await edit(kenvPath()) 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/help/follow.ts: -------------------------------------------------------------------------------- 1 | await open("https://twitter.com/scriptkitapp") 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/help/reveal-kenv.ts: -------------------------------------------------------------------------------- 1 | await cli("reveal", kenvPath("scripts")) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/browse-examples.ts: -------------------------------------------------------------------------------- 1 | browse(`https://scriptkit.com/scripts/`) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/change-editor.ts: -------------------------------------------------------------------------------- 1 | await selectKitEditor(true) 2 | await mainScript() 3 | export {} 4 | -------------------------------------------------------------------------------- /src/help/reveal-kit.ts: -------------------------------------------------------------------------------- 1 | await cli("reveal", kitPath("package.json")) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/help/tail-log.ts: -------------------------------------------------------------------------------- 1 | await terminal(`tail -f ~/.kit/logs/main.log`) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/setup/ensure-snippets.ts: -------------------------------------------------------------------------------- 1 | await ensureDir(kenvPath("snippets")) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /root/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flowerskitchen/kit2/HEAD/root/images/icon.png -------------------------------------------------------------------------------- /root/images/kent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flowerskitchen/kit2/HEAD/root/images/kent.jpg -------------------------------------------------------------------------------- /root/images/matt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flowerskitchen/kit2/HEAD/root/images/matt.jpg -------------------------------------------------------------------------------- /scripts/dist.js: -------------------------------------------------------------------------------- 1 | let distPath = process.env.DIST_PATH || home(".kit") 2 | cp("bin", distPath) 3 | -------------------------------------------------------------------------------- /scripts/script-with-export.js: -------------------------------------------------------------------------------- 1 | let value = await arg() 2 | export default { 3 | value, 4 | } 5 | -------------------------------------------------------------------------------- /src/cli/authenticate.ts: -------------------------------------------------------------------------------- 1 | import { authenticate } from "../api/kit.js" 2 | 3 | await authenticate() -------------------------------------------------------------------------------- /src/cli/goto-docs.ts: -------------------------------------------------------------------------------- 1 | exec( 2 | `open https://scriptkit.com/docs` 3 | ) 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/cli/goto-guide.ts: -------------------------------------------------------------------------------- 1 | exec( 2 | `open https://scriptkit.com/guide` 3 | ) 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/config/set-login.ts: -------------------------------------------------------------------------------- 1 | await cli("set-env-var", "KIT_LOGIN", await arg()) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/help/reveal-kit-log.ts: -------------------------------------------------------------------------------- 1 | await cli('reveal', kitPath('logs', 'main.log')) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/get-help.ts: -------------------------------------------------------------------------------- 1 | open("https://github.com/johnlindquist/kit/discussions") 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/cli/toggle-tray.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | send(Channel.TOGGLE_TRAY) 4 | -------------------------------------------------------------------------------- /src/config/toggle-sponsor.ts: -------------------------------------------------------------------------------- 1 | await cli("set-env-var", "KIT_PRO", await arg()) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/main/help.ts: -------------------------------------------------------------------------------- 1 | // Exclude: true 2 | 3 | open(`https://github.com/johnlindquist/kit/discussions`) 4 | -------------------------------------------------------------------------------- /src/config/set-accessible.ts: -------------------------------------------------------------------------------- 1 | await cli("set-env-var", "KIT_ACCESSIBILITY", "true") 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | install-links=false 3 | save-exact=true 4 | use-node-version=22.9.0 -------------------------------------------------------------------------------- /root/images/stream-deck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flowerskitchen/kit2/HEAD/root/images/stream-deck.png -------------------------------------------------------------------------------- /src/help/install-vscode-extension.ts: -------------------------------------------------------------------------------- 1 | open(`vscode:extension/johnlindquist.kit-extension`) 2 | 3 | export {} 4 | -------------------------------------------------------------------------------- /src/api/packages/tmpPromise.ts: -------------------------------------------------------------------------------- 1 | import tmpPromise from "tmp-promise" 2 | ;(global as any).tmpPromise = tmpPromise 3 | -------------------------------------------------------------------------------- /src/cli/notify.ts: -------------------------------------------------------------------------------- 1 | notify({ 2 | icon: kitPath("images", "icon.png"), 3 | title: arg?.title || "Script Kit" 4 | }) 5 | -------------------------------------------------------------------------------- /src/cli/open-log.ts: -------------------------------------------------------------------------------- 1 | // Description: Open Log 2 | 3 | await edit(kitPath("logs", "main.log")) 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/cli/update.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | send(Channel.UPDATE_APP) 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/globals/globby.ts: -------------------------------------------------------------------------------- 1 | import { globby as _globby } from "globby" 2 | export let globby = (global.globby = _globby) 3 | -------------------------------------------------------------------------------- /src/globals/crypto.ts: -------------------------------------------------------------------------------- 1 | import crypto from "node:crypto" 2 | 3 | export let uuid = (global.uuid = crypto.randomUUID) 4 | -------------------------------------------------------------------------------- /src/globals/download.ts: -------------------------------------------------------------------------------- 1 | import _download from "download" 2 | 3 | export let download = (global.download = _download) 4 | -------------------------------------------------------------------------------- /root/bin/k: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | KIT=$(cd "$(dirname ${BASH_SOURCE[0]})"/.. &>/dev/null && pwd) 4 | $KIT/script "$@" 5 | -------------------------------------------------------------------------------- /src/cli/main-log.ts: -------------------------------------------------------------------------------- 1 | //Description: Script Kit CLI 2 | await edit(kitPath("logs", "main.log"), kitPath()) 3 | 4 | export {} 5 | -------------------------------------------------------------------------------- /src/cli/manual-npm.ts: -------------------------------------------------------------------------------- 1 | let command = "" 2 | 3 | await term({ 4 | command, 5 | cwd: kenvPath() 6 | }) 7 | 8 | export {} 9 | -------------------------------------------------------------------------------- /src/cli/quit.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | await hide() 4 | send(Channel.QUIT_APP) 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /src/cli/refresh-scripts-db.ts: -------------------------------------------------------------------------------- 1 | import { refreshScripts } from "../core/db.js" 2 | 3 | await refreshScripts() 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/globals/handlebars.ts: -------------------------------------------------------------------------------- 1 | import handlebars from "handlebars" 2 | 3 | export let compile = (global.compile = handlebars.compile) 4 | -------------------------------------------------------------------------------- /src/help/authorized-info.ts: -------------------------------------------------------------------------------- 1 | exec( 2 | `open 'https://github.com/johnlindquist/kit/discussions/785'` 3 | ) 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "arrowParens": "avoid", 4 | "bracketSameLine": false, 5 | "printWidth": 60 6 | } -------------------------------------------------------------------------------- /src/handler/comma-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("sticky") 3 | -------------------------------------------------------------------------------- /src/handler/equals-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("dev") 3 | -------------------------------------------------------------------------------- /src/handler/question-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("help") 3 | -------------------------------------------------------------------------------- /src/handler/quote-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("snippets") 3 | -------------------------------------------------------------------------------- /src/handler/tilde-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("suggest") 3 | -------------------------------------------------------------------------------- /src/handler/zero-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("emoji") 3 | -------------------------------------------------------------------------------- /SIGN_IN.md: -------------------------------------------------------------------------------- 1 | # Sign in with GitHub 2 | 3 | ## Unlock Community Features 4 | 5 | - Share Scripts 6 | - Create Gists 7 | - Discord Server Invite -------------------------------------------------------------------------------- /root/templates/scripts/snippet.js: -------------------------------------------------------------------------------- 1 | // Name: 2 | // Snippet: 3 | 4 | import "@johnlindquist/kit" 5 | 6 | await keyboard.type("Change me!") 7 | -------------------------------------------------------------------------------- /root/templates/scripts/snippet.ts: -------------------------------------------------------------------------------- 1 | // Name: 2 | // Snippet: 3 | 4 | import "@johnlindquist/kit" 5 | 6 | await keyboard.type("Change me!") 7 | -------------------------------------------------------------------------------- /src/api/packages/open.ts: -------------------------------------------------------------------------------- 1 | import open, { openApp } from "@johnlindquist/open" 2 | ;(global as any).open = open 3 | global.openApp = openApp 4 | -------------------------------------------------------------------------------- /src/api/packages/zx.ts: -------------------------------------------------------------------------------- 1 | // This was moved to the target/app and target/terminal 2 | // Some global in "app.ts" is interferring with this 3 | -------------------------------------------------------------------------------- /src/cli/edit-file.ts: -------------------------------------------------------------------------------- 1 | // Description: Opens a file in your editor 2 | 3 | await edit(await arg("Enter a file path:")) 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/handler/backtick-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("google") 3 | -------------------------------------------------------------------------------- /src/handler/colon-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("focus-window") 3 | -------------------------------------------------------------------------------- /src/handler/greaterthan-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("term") 3 | -------------------------------------------------------------------------------- /src/handler/number-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("calculator") 3 | -------------------------------------------------------------------------------- /src/handler/period-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("file-search") 3 | -------------------------------------------------------------------------------- /src/handler/pipe-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("window-manager") 3 | -------------------------------------------------------------------------------- /src/handler/slash-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("browse", "~") 3 | -------------------------------------------------------------------------------- /src/main/dev.ts: -------------------------------------------------------------------------------- 1 | // Name: Dev Tools 2 | // Description: Open a Dev Tools Playground 3 | // Trigger: = 4 | 5 | await dev() 6 | export {} 7 | -------------------------------------------------------------------------------- /src/cli/clear.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | send(Channel.CLEAR_CACHE) 4 | 5 | await mainScript() 6 | 7 | export {} 8 | -------------------------------------------------------------------------------- /src/globals/replace-in-file.ts: -------------------------------------------------------------------------------- 1 | import { replaceInFile } from "replace-in-file" 2 | 3 | export let replace = (global.replace = replaceInFile) 4 | -------------------------------------------------------------------------------- /src/handler/backslash-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("window-manager") 3 | -------------------------------------------------------------------------------- /src/handler/doublequote-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("datamuse") 3 | -------------------------------------------------------------------------------- /src/handler/leftbracket-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("templates") 3 | -------------------------------------------------------------------------------- /src/handler/minus-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("system-commands") 3 | -------------------------------------------------------------------------------- /src/handler/semicolon-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("app-launcher") 3 | -------------------------------------------------------------------------------- /src/cli/toggle-auto-update.ts: -------------------------------------------------------------------------------- 1 | import { toggleEnvVar } from "../api/kit.js" 2 | 3 | await toggleEnvVar("KIT_AUTO_UPDATE", "true") 4 | 5 | export {} 6 | -------------------------------------------------------------------------------- /src/handler/lessthan-handler.ts: -------------------------------------------------------------------------------- 1 | import { runUserHandlerIfExists } from "../cli/lib/utils.js" 2 | await runUserHandlerIfExists("clipboard-history") 3 | -------------------------------------------------------------------------------- /root/override/stackblitz/kit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | NODE_NO_WARNINGS=1 \ 4 | node \ 5 | ./node_modules/@johnlindquist/kit/run/stackblitz.js 6 | -------------------------------------------------------------------------------- /src/api/recent.ts: -------------------------------------------------------------------------------- 1 | export let getRecentLimit = () => { 2 | return Number.parseInt( 3 | process.env?.KIT_RECENT_LIMIT || "3", 4 | 10 5 | ) 6 | } 7 | -------------------------------------------------------------------------------- /src/setup/clone-docs.ts: -------------------------------------------------------------------------------- 1 | cd(kenvPath("kenvs")) 2 | await exec( 3 | `git clone https://github.com/johnlindquist/kit-docs docs` 4 | ) 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /src/setup/ensure-scriptlets.ts: -------------------------------------------------------------------------------- 1 | await ensureDir(kenvPath("scriptlets")) 2 | await ensureFile(kenvPath("scriptlets", "scriptlets.md")) 3 | 4 | export {} 5 | -------------------------------------------------------------------------------- /root/sk: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | KIT=$(cd "$(dirname ${BASH_SOURCE[0]})" &> /dev/null && pwd) 3 | curl --http0.9 --unix-socket $KIT/kit.sock "http://localhost/$@" 4 | 5 | -------------------------------------------------------------------------------- /src/debug/test-notification.ts: -------------------------------------------------------------------------------- 1 | notify({ 2 | title: "Kit SDK Notification", 3 | body: "This is a test notification from the Kit SDK" 4 | }) 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /root/override/code/python: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! command -v python3 >/dev/null 2>&1; then 4 | /usr/bin/python "$@" 5 | else 6 | python3 "$@" 7 | fi 8 | -------------------------------------------------------------------------------- /src/cli/installs.ts: -------------------------------------------------------------------------------- 1 | let installInfo = JSON.parse(await arg()) 2 | 3 | await post( 4 | `https://scriptkit.com/api/installs`, 5 | installInfo 6 | ) 7 | 8 | export {} 9 | -------------------------------------------------------------------------------- /src/cli/build-widget.ts: -------------------------------------------------------------------------------- 1 | import { buildWidget } from "../api/kit.js" 2 | 3 | let scriptPath = await arg("Path to Widget:") 4 | await buildWidget(scriptPath) 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /src/cli/postinstall.ts: -------------------------------------------------------------------------------- 1 | console.log(`Starting postinstall`) 2 | 3 | let tmpScriptsPath = kitPath("tmp", "scripts") 4 | cp(kenvPath("scripts/*"), tmpScriptsPath) 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /src/api/packages/clipboardy.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../../core/enum.js" 2 | 3 | global.paste = () => sendWait(Channel.PASTE) 4 | global.copy = text => sendWait(Channel.COPY, text) 5 | -------------------------------------------------------------------------------- /src/main/rhyme.ts: -------------------------------------------------------------------------------- 1 | // Name: Rhyme 2 | // Keyword: rhyme 3 | // Description: Find rhyming words 4 | 5 | await run(kitPath("main", "datamuse.js"), "--fn", "rhyme") 6 | 7 | export {} 8 | -------------------------------------------------------------------------------- /src/main/define.ts: -------------------------------------------------------------------------------- 1 | // Name: Define 2 | // Description: Lookup the definition of a word 3 | // Keyword: d 4 | 5 | await run(kitPath("main", "datamuse.js"), "--fn", "syn") 6 | 7 | export {} 8 | -------------------------------------------------------------------------------- /src/cli/open-command-log.ts: -------------------------------------------------------------------------------- 1 | let { filePath, command } = await selectScript( 2 | `Open log for which script?` 3 | ) 4 | 5 | await edit(kenvPath("logs", `${command}.log`)) 6 | 7 | export {} 8 | -------------------------------------------------------------------------------- /root/templates/env/template.env: -------------------------------------------------------------------------------- 1 | KIT={{KIT}} 2 | KENV={{KENV}} 3 | KIT_NODE_PATH={{KIT_NODE_PATH}} 4 | KIT_TEMPLATE=default 5 | KIT_METADATA_MODE=comment 6 | KIT_MAIN_SHORTCUT="{{KIT_MAIN_SHORTCUT}}" 7 | -------------------------------------------------------------------------------- /src/app/toggle-watcher.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | await sendWait(Channel.TOGGLE_WATCHER) 4 | 5 | await wait(3000) 6 | 7 | await mainScript() 8 | 9 | export {} 10 | -------------------------------------------------------------------------------- /src/cli/switch-to-js.ts: -------------------------------------------------------------------------------- 1 | // Description: Switch to JavaScript Mode 2 | 3 | await global.cli("set-env-var", "KIT_MODE", "js") 4 | process.env.KIT_MODE = "js" 5 | await mainScript() 6 | 7 | export {} 8 | -------------------------------------------------------------------------------- /src/cli/sync-path-instructions.ts: -------------------------------------------------------------------------------- 1 | let scriptPath = kitPath("cli", "sync-path-node.js") 2 | let envPath = kenvPath(".env") 3 | 4 | await term(`pnpm node ${scriptPath} ${envPath}`) 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /root/templates/scripts/clipboard-template.js: -------------------------------------------------------------------------------- 1 | // Template: true 2 | 3 | import "@johnlindquist/kit" 4 | 5 | let template = ` 6 | {{template}} 7 | `.trim() 8 | 9 | setSelectedText(template) 10 | -------------------------------------------------------------------------------- /root/templates/scripts/clipboard-template.ts: -------------------------------------------------------------------------------- 1 | // Template: true 2 | 3 | import "@johnlindquist/kit" 4 | 5 | let template = ` 6 | {{template}} 7 | `.trim() 8 | 9 | setSelectedText(template) 10 | -------------------------------------------------------------------------------- /root/icons/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/cli/info.ts: -------------------------------------------------------------------------------- 1 | let name = await arg() 2 | let description = await arg() 3 | 4 | let html = md(await arg()) 5 | 6 | await div({ 7 | name, 8 | description, 9 | html, 10 | }) 11 | 12 | export {} 13 | -------------------------------------------------------------------------------- /src/cli/issue.ts: -------------------------------------------------------------------------------- 1 | // Description: Launches a browser tab to "https://github.com/johnlindquist/.js/issues/new" 2 | 3 | exec( 4 | `open "https://github.com/johnlindquist/kit/issues/new"` 5 | ) 6 | 7 | export {} 8 | -------------------------------------------------------------------------------- /dtsgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": { 3 | "files": ["./src/types/index.d.ts"], 4 | "urls": [], 5 | "stdin": true 6 | }, 7 | "target": "esnext", 8 | "outputAST": false, 9 | "plugins": {} 10 | } 11 | -------------------------------------------------------------------------------- /root/templates/config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test-sdk/ava.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | environmentVariables: { 3 | KIT_TEST: "true", 4 | }, 5 | verbose: true, 6 | files: [ 7 | "src/**/*.test.js" 8 | "test/**/*.test.js", 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /src/cli/pull-script.ts: -------------------------------------------------------------------------------- 1 | let { filePath } = await selectScript( 2 | "Select Script to Pull Kenv" 3 | ) 4 | 5 | let kPath = path.dirname(path.dirname(filePath)) 6 | 7 | await cli("kenv-pull", kPath) 8 | 9 | export {} 10 | -------------------------------------------------------------------------------- /src/cli/push-script.ts: -------------------------------------------------------------------------------- 1 | let { filePath } = await selectScript( 2 | "Select Script to Push Kenv" 3 | ) 4 | 5 | let kPath = path.dirname(path.dirname(filePath)) 6 | 7 | await cli("kenv-push", kPath) 8 | 9 | export {} 10 | -------------------------------------------------------------------------------- /src/globals/chalk.ts: -------------------------------------------------------------------------------- 1 | import _chalk from "chalk" 2 | import _chalkTemplate from "chalk-template" 3 | // TODO: Upgrade to Chalk v5 once the templates are supported 4 | export let chalk = ((global as any).chalk = _chalkTemplate) 5 | -------------------------------------------------------------------------------- /src/main/emoji.ts: -------------------------------------------------------------------------------- 1 | // Name: Emoji Picker 2 | // Description: Select an Emoji to Paste 3 | // Keyword: e 4 | // Cache: true 5 | 6 | let { emoji: e } = await emoji() 7 | 8 | if (e) await setSelectedText(e) 9 | export {} 10 | -------------------------------------------------------------------------------- /root/templates/scripts/template.js: -------------------------------------------------------------------------------- 1 | // Template: true 2 | 3 | import "@johnlindquist/kit" 4 | 5 | let name = await arg("Enter name") 6 | 7 | let template = ` 8 | Hello, ${name}! 9 | `.trim() 10 | 11 | setSelectedText(template) 12 | -------------------------------------------------------------------------------- /root/templates/scripts/template.ts: -------------------------------------------------------------------------------- 1 | // Template: true 2 | 3 | import "@johnlindquist/kit" 4 | 5 | let name = await arg("Enter name") 6 | 7 | let template = ` 8 | Hello, ${name}! 9 | `.trim() 10 | 11 | setSelectedText(template) 12 | -------------------------------------------------------------------------------- /root/templates/bin/cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set NODE_NO_WARNINGS=1 3 | set KENV=%~dp0.. 4 | set KIT={{KIT}} 5 | set TARGET_PATH={{TARGET_PATH}} 6 | 7 | {{KIT_NODE_PATH}} --loader file://%KIT%\build\loader.js %KIT%\run\terminal.js %TARGET_PATH% %* 8 | -------------------------------------------------------------------------------- /root/templates/bin/template: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NODE_NO_WARNINGS=1 \ 4 | KENV=$(cd "$(dirname "$0")"/.. &> /dev/null && pwd) \ 5 | {{KIT_NODE_PATH}} \ 6 | --loader {{KIT}}/build/loader.js \ 7 | {{KIT}}/run/terminal.js \ 8 | {{TARGET_PATH}} \ 9 | "$@" -------------------------------------------------------------------------------- /root/templates/bin/terminal: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NODE_NO_WARNINGS=1 \ 4 | KENV=$(cd "$(dirname "$0")"/.. &> /dev/null && pwd) \ 5 | {{KIT_NODE_PATH}} \ 6 | --loader {{KIT}}/build/loader.js \ 7 | {{KIT}}/run/terminal.js \ 8 | {{TARGET_PATH}} \ 9 | "$@" -------------------------------------------------------------------------------- /src/permissions/snippets.ts: -------------------------------------------------------------------------------- 1 | await div({ 2 | html: md( 3 | ` 4 | # Snippets Requires Permissions 5 | 6 | Please grant accessibility permissions to Kit.app then restart Kit.app 7 | `.trim() 8 | ), 9 | }) 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/permissions/clipboard-history.ts: -------------------------------------------------------------------------------- 1 | await div( 2 | md( 3 | ` 4 | # Clipboard History Requires Permissions 5 | 6 | Please grant accessibility permissions to Kit.app then restart Kit.app 7 | `.trim() 8 | ) 9 | ) 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/cli/edit.ts: -------------------------------------------------------------------------------- 1 | // Description: Opens the selected script in your editor 2 | 3 | let { filePath } = await selectScript( 4 | `Select script to open in ${await env("KIT_EDITOR")}?` 5 | ) 6 | 7 | await edit(filePath, kenvPath()) 8 | 9 | export {} 10 | -------------------------------------------------------------------------------- /src/cli/tail-log.ts: -------------------------------------------------------------------------------- 1 | import "@johnlindquist/kit" 2 | import { getLogFromScriptPath } from "../core/utils.js" 3 | 4 | let scriptPath = await arg("Script Path") 5 | 6 | let logPath = getLogFromScriptPath(scriptPath) 7 | await terminal(`tail -f ${logPath}`) 8 | -------------------------------------------------------------------------------- /src/workers/kit-worker.ts: -------------------------------------------------------------------------------- 1 | import { parentPort } from "node:worker_threads" 2 | import { run } from "../core/utils.js" 3 | 4 | parentPort?.on("message", async ({ filePath }) => { 5 | await run(filePath) 6 | 7 | parentPort?.postMessage({ filePath }) 8 | }) 9 | -------------------------------------------------------------------------------- /src/cli/disable-telemetry.ts: -------------------------------------------------------------------------------- 1 | // Description: Disable Telemetry 2 | 3 | await global.cli( 4 | "set-env-var", 5 | "KIT_DISABLE_TELEMETRY", 6 | "true" 7 | ) 8 | process.env.KIT_DISABLE_TELEMETRY = "true" 9 | await mainScript() 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | debounce, 3 | isString, 4 | isUndefined, 5 | sortBy, 6 | } from "../core/utils.js" 7 | 8 | global.debounce = debounce 9 | global.sortBy = sortBy 10 | global.isUndefined = isUndefined 11 | global.isString = isString 12 | -------------------------------------------------------------------------------- /src/cli/disable-auto-update.ts: -------------------------------------------------------------------------------- 1 | // Description: Disable Auto Update 2 | 3 | await global.cli( 4 | "set-env-var", 5 | "KIT_DISABLE_AUTO_UPDATE", 6 | "true" 7 | ) 8 | process.env.KIT_DISABLE_AUTO_UPDATE = "true" 9 | await mainScript() 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/globals/child_process.ts: -------------------------------------------------------------------------------- 1 | import child_process from "node:child_process" 2 | 3 | export let spawn = (global.spawn = child_process.spawn) 4 | export let spawnSync = (global.spawnSync = child_process.spawnSync) 5 | export let fork = (global.fork = child_process.fork) 6 | -------------------------------------------------------------------------------- /src/cli/open-app.ts: -------------------------------------------------------------------------------- 1 | console.log(`open-app spawned`) 2 | await wait(5000) 3 | 4 | console.log(`Re-opening Kit.app`) 5 | let { stdout, stderr } = await exec( 6 | global.isWin 7 | ? `start Kit.exe` 8 | : `open /Applications/Kit.app` 9 | ) 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/setup/setup.ts: -------------------------------------------------------------------------------- 1 | await setup("create-env") 2 | await setup("link-kenv-to-kit") 3 | await setup("chmod-helpers") 4 | await setup("switch-windows-kit-to-bat") 5 | await setup("ensure-snippets") 6 | await setup("ensure-scriptlets") 7 | // await setup("setup-pnpm") 8 | 9 | export {} 10 | -------------------------------------------------------------------------------- /scripts/test-post.js: -------------------------------------------------------------------------------- 1 | import { rimraf } from "rimraf" 2 | 3 | await import("../test-sdk/config.js") 4 | 5 | if (test("-d", kitMockPath())) { 6 | await rimraf(kitMockPath()) 7 | } 8 | 9 | process.env.KENV = home(".kenv") 10 | await exec(`kit ${kitPath("cli", "refresh-scripts-db.js")}`) 11 | -------------------------------------------------------------------------------- /src/globals/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | export let get = (global.get = axios.get) 4 | export let put = (global.put = axios.put) 5 | export let post = (global.post = axios.post) 6 | export let patch = (global.patch = axios.patch) 7 | export let del = (global.delete = axios.delete) 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Install Failed? Please check this discussion first! 11 | 12 | https://github.com/johnlindquist/kit/discussions/1052 13 | -------------------------------------------------------------------------------- /scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from "rimraf" 2 | let scriptDirs = [ 3 | "api", 4 | "ci", 5 | "cli", 6 | "ipc", 7 | "lib", 8 | "main", 9 | "setup", 10 | ] 11 | 12 | scriptDirs.forEach(dir => { 13 | rimraf(dir, [], () => { 14 | console.log(`${dir} removed`) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/cli/clear-recent.ts: -------------------------------------------------------------------------------- 1 | // Description: Clear Timestamps 2 | 3 | import { refreshScripts } from "../core/db.js" 4 | 5 | let script = await selectScript(`Remove a script:`) 6 | 7 | await global.clearTimestamps() 8 | await refreshScripts() 9 | 10 | await mainScript() 11 | 12 | export {} 13 | -------------------------------------------------------------------------------- /src/cli/error-prompt.ts: -------------------------------------------------------------------------------- 1 | // Name: Error 2 | // Description: An error has occurred 3 | 4 | import { errorPrompt } from "../api/kit.js" 5 | 6 | let error = await new Promise(resolve => 7 | process.on("message", resolve) 8 | ) 9 | 10 | await errorPrompt(error as Error) 11 | 12 | export {} 13 | -------------------------------------------------------------------------------- /src/cli/prefs.ts: -------------------------------------------------------------------------------- 1 | import { getPrefs } from "../core/db.js" 2 | 3 | let kitPrefs = await getPrefs() 4 | 5 | let selectedSetting = await arg("Which setting") 6 | let value = await arg("Set to what?") 7 | 8 | kitPrefs.data[selectedSetting] = value 9 | await kitPrefs.write() 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/globals/stream.ts: -------------------------------------------------------------------------------- 1 | import Stream from "node:stream" 2 | 3 | export let Writable = (global.Writable = Stream.Writable) 4 | export let Readable = (global.Readable = Stream.Readable) 5 | export let Duplex = (global.Duplex = Stream.Duplex) 6 | export let Transform = (global.Transform = Stream.Transform) 7 | -------------------------------------------------------------------------------- /src/cli/kit-clear-prompt.ts: -------------------------------------------------------------------------------- 1 | import { getScripts, getTimestamps } from "../core/db.js" 2 | import { Channel } from "../core/enum.js" 3 | 4 | await sendWait(Channel.CLEAR_PROMPT_CACHE) 5 | setInput(``) 6 | await getTimestamps(false) 7 | await getScripts(false) 8 | 9 | await mainScript() 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /test/ava.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | workerThreads: false, 3 | extensions: { 4 | ts: "module", 5 | }, 6 | nodeArguments: ["--import=tsx"], 7 | environmentVariables: { 8 | KIT_TEST: "true", 9 | }, 10 | verbose: true, 11 | files: ["src/**/*.test.ts", "test/**/*.test.ts"], 12 | } 13 | -------------------------------------------------------------------------------- /src/cli/feedback.ts: -------------------------------------------------------------------------------- 1 | let formData = JSON.parse(await arg()) 2 | 3 | await post(`https://scriptkit.com/api/feedback`, formData) 4 | 5 | if (formData?.email && formData?.subscribe) { 6 | await post(`https://scriptkit.com/api/subscribe`, { 7 | email_address: formData?.email, 8 | }) 9 | } 10 | 11 | export {} 12 | -------------------------------------------------------------------------------- /src/cli/reveal.ts: -------------------------------------------------------------------------------- 1 | // Description: Reveal the selected script in Finder 2 | 3 | let filePath = await arg("Path to file to reveal") 4 | await open(path.dirname(filePath)) 5 | await applescript(` 6 | set aFile to (POSIX file "${filePath}") as alias 7 | tell application "Finder" to select aFile 8 | `) 9 | 10 | export {} 11 | -------------------------------------------------------------------------------- /root/icons/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/lib.ts: -------------------------------------------------------------------------------- 1 | await import("../lib/audio.js") 2 | await import("../lib/browser.js") 3 | await import("../lib/desktop.js") 4 | await import("../lib/file.js") 5 | await import("../lib/keyboard.js") 6 | await import("../lib/system.js") 7 | await import("../lib/text.js") 8 | await import("../lib/utils.js") 9 | 10 | export {} 11 | -------------------------------------------------------------------------------- /src/main/sign-in.ts: -------------------------------------------------------------------------------- 1 | // Name: Sign In to GitHub 2 | // Description: Authenticate with GitHub to Enable Features 3 | // Enter: View Account 4 | // PreviewPath: $KIT/SIGN_IN.md 5 | // Pass: true 6 | 7 | import { authenticate } from "../api/kit.js" 8 | 9 | hide() 10 | 11 | await authenticate() 12 | 13 | await mainScript() 14 | -------------------------------------------------------------------------------- /src/globals/process.ts: -------------------------------------------------------------------------------- 1 | export let cwd = (global.cwd = process.cwd) 2 | export let pid = (global.pid = process.pid) 3 | export let stderr = (global.stderr = process.stderr) 4 | export let stdin = (global.stdin = process.stdin) 5 | export let stdout = (global.stdout = process.stdout) 6 | export let uptime = (global.uptime = process.uptime) 7 | -------------------------------------------------------------------------------- /src/setup/chmod-helpers.ts: -------------------------------------------------------------------------------- 1 | try { 2 | chmod(755, kitPath("script")) 3 | chmod(755, kitPath("kar")) 4 | chmod(755, kitPath("bin", "k")) 5 | chmod(755, kitPath("bin", "kit")) 6 | chmod(755, kitPath("bin", "sk")) 7 | chmod(755, kitPath("override", "code", "python")) 8 | } catch (e) { 9 | console.error(e) 10 | } 11 | 12 | export {} 13 | -------------------------------------------------------------------------------- /src/cli/remove-from-recent.ts: -------------------------------------------------------------------------------- 1 | // Description: Remove a script timestamp 2 | 3 | import { refreshScripts } from "../core/db.js" 4 | 5 | let script = await selectScript(`Remove a script:`) 6 | 7 | let { filePath } = script 8 | await global.removeTimestamp(filePath) 9 | await refreshScripts() 10 | 11 | await mainScript() 12 | 13 | export {} 14 | -------------------------------------------------------------------------------- /src/workers/index.ts: -------------------------------------------------------------------------------- 1 | import { kitPath } from "../core/utils.js" 2 | 3 | export const CACHED_GROUPED_SCRIPTS_WORKER = kitPath( 4 | "workers", 5 | "cache-grouped-scripts-worker.js" 6 | ) 7 | 8 | export const CREATE_BIN_WORKER = kitPath("workers", "create-bin-worker.js") 9 | 10 | export const KIT_WORKER = kitPath("workers", "kit-worker.js") 11 | -------------------------------------------------------------------------------- /src/cli/copy-path.ts: -------------------------------------------------------------------------------- 1 | //Menu: Copy Script to Clipboard 2 | //Description: Copies Script to Clipboard 3 | 4 | let { filePath } = await selectScript(`Share which script?`) 5 | 6 | copy(filePath) 7 | div( 8 | md( 9 | `${filePath} copied to clipboard` 10 | ) 11 | ) 12 | await wait(2000, null) 13 | 14 | export {} 15 | -------------------------------------------------------------------------------- /src/main/templates.ts: -------------------------------------------------------------------------------- 1 | // Description: Templates 2 | // Exclude: true 3 | 4 | import { 5 | escapeShortcut, 6 | closeShortcut, 7 | } from "../core/utils.js" 8 | setName(``) 9 | 10 | await arg({ 11 | placeholder: "Reserved for future use", 12 | enter: "Exit", 13 | shortcuts: [escapeShortcut, closeShortcut], 14 | }) 15 | 16 | export {} 17 | -------------------------------------------------------------------------------- /src/cli/kenv-switch.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | let kitAppDb = await db<{ KENVS: string[] }>(kitPath("db", "app.json")) 4 | 5 | let kenv = await arg( 6 | { 7 | placeholder: "Select kenv", 8 | hint: `Current Kenv: ${process.env.KENV}` 9 | }, 10 | kitAppDb.KENVS 11 | ) 12 | 13 | global.send(Channel.SWITCH_KENV, kenv) 14 | -------------------------------------------------------------------------------- /src/cli/open-script-log.ts: -------------------------------------------------------------------------------- 1 | import { getLogFromScriptPath } from "../core/utils.js" 2 | 3 | let { filePath } = await selectScript( 4 | `Open log for which script?` 5 | ) 6 | 7 | let logPath = getLogFromScriptPath(filePath) 8 | log(`Opening log for ${filePath}: ${logPath}`) 9 | await ensureFile(logPath) 10 | 11 | await edit(logPath) 12 | 13 | export {} 14 | -------------------------------------------------------------------------------- /src/hot/download-hot.ts: -------------------------------------------------------------------------------- 1 | // Description: Download latest hot 2 | 3 | try { 4 | await download( 5 | `https://www.scriptkit.com/api/hot`, 6 | kitPath("data"), 7 | { 8 | rejectUnauthorized: false, 9 | } 10 | ) 11 | global.log(`🔥 Hot updated`) 12 | } catch { 13 | global.warn(`Hot failed to download`) 14 | } 15 | 16 | export {} 17 | -------------------------------------------------------------------------------- /src/cli/new-from-clipboard.ts: -------------------------------------------------------------------------------- 1 | // Description: Creates a new empty script you can invoke from the terminal 2 | 3 | import { parseMetadata } from "../core/utils.js" 4 | 5 | let content = await paste() 6 | 7 | let { name } = parseMetadata(content) 8 | if (name) { 9 | arg.pass = name 10 | } 11 | 12 | arg.tip = content 13 | await cli("new") 14 | 15 | export {} 16 | -------------------------------------------------------------------------------- /src/help/download-docs.ts: -------------------------------------------------------------------------------- 1 | // Description: Download latest docs 2 | 3 | try { 4 | await download( 5 | `https://www.scriptkit.com/api/docs`, 6 | kitPath("data"), 7 | { 8 | rejectUnauthorized: false, 9 | } 10 | ) 11 | 12 | global.log(`📝 Docs updated`) 13 | } catch { 14 | global.warn(`Docs failed to download`) 15 | } 16 | export {} 17 | -------------------------------------------------------------------------------- /src/cli/reveal-script.ts: -------------------------------------------------------------------------------- 1 | // Description: Reveal the selected script in Finder 2 | 3 | let filePath = "" 4 | if (typeof args[0] === "string") { 5 | filePath = args[0] 6 | } else { 7 | let script = await selectScript( 8 | `Which script do you want to reveal?` 9 | ) 10 | filePath = script.filePath 11 | } 12 | await revealFile(filePath) 13 | 14 | export {} 15 | -------------------------------------------------------------------------------- /src/emoji/download-emoji.ts: -------------------------------------------------------------------------------- 1 | // Description: Download latest hot 2 | 3 | try { 4 | await download( 5 | `https://www.scriptkit.com/api/emoji`, 6 | kitPath("data"), 7 | { 8 | rejectUnauthorized: false, 9 | } 10 | ) 11 | global.log(`😘 Emoji updated`) 12 | } catch { 13 | global.warn(`Emoji failed to download`) 14 | } 15 | 16 | export {} 17 | -------------------------------------------------------------------------------- /root/logos/vercel/symbol-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /root/logos/vercel/symbol-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/setup/clone-sponsors.ts: -------------------------------------------------------------------------------- 1 | let sponsorsDir = kenvPath('kenvs', 'sponsors') 2 | 3 | if (await isDir(sponsorsDir)) { 4 | await exec('git pull --rebase --autostash --stat', { 5 | cwd: sponsorsDir 6 | }) 7 | } else { 8 | await exec('git clone --depth 1 https://github.com/johnlindquist/kit-sponsors sponsors', { 9 | cwd: kenvPath('kenvs') 10 | }) 11 | } 12 | 13 | export type {} 14 | -------------------------------------------------------------------------------- /src/cli/open-script-database.ts: -------------------------------------------------------------------------------- 1 | let { filePath, command } = await selectScript( 2 | `Open database for which script?` 3 | ) 4 | 5 | let scriptDb = path.resolve( 6 | path.dirname(path.dirname(filePath)), 7 | "db", 8 | `_${command}.json` 9 | ) 10 | 11 | await ensureReadFile( 12 | scriptDb, 13 | JSON.stringify({ items: [] }) 14 | ) 15 | 16 | await edit(scriptDb) 17 | 18 | export {} 19 | -------------------------------------------------------------------------------- /src/globals/execa.test.ts: -------------------------------------------------------------------------------- 1 | import ava from "ava" 2 | import "../core/utils.js" 3 | 4 | ava("$ works", async t => { 5 | const message = "Hello, world!" 6 | let { stdout } = await $`echo ${message}` 7 | t.is(stdout, message) 8 | }) 9 | 10 | 11 | ava("exec works", async t => { 12 | const message = "Hello, world!" 13 | let { stdout } = await exec(`echo ${message}`) 14 | t.is(stdout, message) 15 | }) 16 | 17 | -------------------------------------------------------------------------------- /tsconfig-declaration.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "moduleResolution": "Node", 8 | "lib": ["esnext"], 9 | "allowSyntheticDefaultImports": true, 10 | "declaration": true, 11 | "emitDeclarationOnly": true, 12 | "skipLibCheck": true 13 | }, 14 | "include": ["./src/core/*"] 15 | } 16 | -------------------------------------------------------------------------------- /src/setup/switch-windows-kit-to-bat.ts: -------------------------------------------------------------------------------- 1 | // If windows, switch the package.json bin/kit to bin/kit.bat 2 | 3 | if (process.platform === "win32") { 4 | const packageJsonPath = kitPath("package.json") 5 | const packageJson = await readJson(packageJsonPath) 6 | 7 | packageJson.bin.kit = "bin/kit.bat" 8 | 9 | await writeFile( 10 | packageJsonPath, 11 | JSON.stringify(packageJson, null, 2) 12 | ) 13 | } 14 | 15 | export {} 16 | -------------------------------------------------------------------------------- /src/cli/create-bin.ts: -------------------------------------------------------------------------------- 1 | import { Bin } from "../core/enum.js" 2 | import type { Script } from "../types/core.ts" 3 | import { createBinFromScript } from "./lib/utils.js" 4 | 5 | let type = await arg( 6 | "Select type:", 7 | Object.values(Bin) 8 | ) 9 | 10 | let script = await selectScript( 11 | "Create bin from which script?", 12 | false 13 | ) 14 | await createBinFromScript(type, { 15 | ...script, 16 | execPath: "", 17 | }) 18 | -------------------------------------------------------------------------------- /scripts/build-bundles.js: -------------------------------------------------------------------------------- 1 | import { readdir } from "fs/promises" 2 | import esbuild from "esbuild" 3 | 4 | let bundles = await readdir("./bundles") 5 | 6 | for (let bundle of bundles) { 7 | esbuild.buildSync({ 8 | entryPoints: [`./bundles/${bundle}`], 9 | treeShaking: true, 10 | bundle: true, 11 | format: "esm", 12 | outfile: `./src/bundles/${bundle}`.replace( 13 | /\.ts$/, 14 | ".js" 15 | ), 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/cli/select-mic.ts: -------------------------------------------------------------------------------- 1 | // Description: Select a Microphone 2 | 3 | let devices = await getMediaDevices() 4 | 5 | let micId = await arg( 6 | "Select Mic", 7 | devices 8 | .filter(d => d.kind === "audioinput") 9 | .map(d => { 10 | return { 11 | name: d.label, 12 | value: d.deviceId, 13 | } 14 | }) 15 | ) 16 | 17 | await run( 18 | kitPath("cli", "set-env-var.js"), 19 | "KIT_MIC", 20 | micId 21 | ) 22 | 23 | export {} 24 | -------------------------------------------------------------------------------- /src/cli/select-webcam.ts: -------------------------------------------------------------------------------- 1 | // Description: Select Webcam 2 | 3 | let devices = await getMediaDevices() 4 | 5 | let webcamId = await arg( 6 | "Select Webcam", 7 | devices 8 | .filter(d => d.kind === "videoinput") 9 | .map(d => { 10 | return { 11 | name: d.label, 12 | value: d.deviceId, 13 | } 14 | }) 15 | ) 16 | 17 | await run( 18 | kitPath("cli", "set-env-var.js"), 19 | "KIT_WEBCAM", 20 | webcamId 21 | ) 22 | 23 | export {} 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "moduleResolution": "Node", 8 | "lib": ["esnext"], 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "skipLibCheck": true, 12 | "sourceMap": true, 13 | }, 14 | "exclude": ["./root/**", "./build/**", "./node_modules/**", "./src/types/kit-editor.d.ts", "**/*.test.*"] 15 | } 16 | -------------------------------------------------------------------------------- /root/bin/sk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | KIT=$(cd "$(dirname ${BASH_SOURCE[0]})"/.. &>/dev/null && pwd) 4 | args="[" 5 | first=true 6 | for var in "${@:2}"; do 7 | if [ $first = true ]; then 8 | first=false 9 | else 10 | args+="," 11 | fi 12 | args+='"'$var'"' 13 | done 14 | args+="]" 15 | 16 | json="{\"script\":\"$1\",\"args\":$args,\"cwd\":\"$(pwd)\"}" 17 | curl --unix-socket $KIT/kit.sock --header "Content-Type: application/json" \ 18 | --data "$json" \ 19 | "http://localhost" 20 | -------------------------------------------------------------------------------- /root/kar: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | KIT=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd) 4 | args="[" 5 | first=true 6 | for var in "${@:2}" 7 | do 8 | if [ $first = true ] 9 | then 10 | first=false 11 | else 12 | args+="," 13 | fi 14 | args+='"'$var'"' 15 | done 16 | args+="]" 17 | 18 | json="{\"script\":\"$1\",\"args\":$args}" 19 | echo $json 20 | curl --http0.9 --unix-socket "$KIT/kit.sock" --header "Content-Type: application/json" \ 21 | --data "$json" \ 22 | "http://localhost" -------------------------------------------------------------------------------- /root/templates/bin/stackblitz: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // These ./bin files are customized for stackblitz 4 | // A local Script Kit install works a little differently 5 | let childProcess = require("child_process") 6 | let path = require("path") 7 | 8 | let [, scriptPath, ..._args] = process.argv 9 | let scriptName = path.basename(scriptPath) 10 | 11 | childProcess.spawnSync( 12 | path.resolve(__dirname, "kit"), 13 | [scriptName, ..._args], 14 | { 15 | stdio: "inherit", 16 | } 17 | ) 18 | -------------------------------------------------------------------------------- /src/cli/share-copy.ts: -------------------------------------------------------------------------------- 1 | //Menu: Copy Script to Clipboard 2 | //Description: Copies Script to Clipboard 3 | 4 | let { filePath } = await selectScript(`Share which script?`) 5 | 6 | let content = await readFile(filePath, "utf8") 7 | copy(content) 8 | 9 | let message = `Copied content of "${path.basename( 10 | filePath 11 | )}" to clipboard` 12 | 13 | setAlwaysOnTop(true) 14 | await div( 15 | await highlight(`## ${message} 16 | 17 | ~~~js 18 | ${content} 19 | ~~~ 20 | `) 21 | ) 22 | 23 | export {} 24 | -------------------------------------------------------------------------------- /src/cli/kenv-term.ts: -------------------------------------------------------------------------------- 1 | import { getKenvs } from "../core/utils.js" 2 | 3 | let kenvs = (await getKenvs()).map(value => ({ 4 | name: path.basename(value), 5 | value, 6 | })) 7 | 8 | kenvs.unshift({ 9 | name: "main", 10 | value: kenvPath(), 11 | }) 12 | 13 | let dir = await arg("Open which kenv", kenvs) 14 | 15 | cd(dir) 16 | 17 | await term({ 18 | description: `Kenv: ${path.basename(dir)}`, 19 | cwd: dir, 20 | }) 21 | 22 | await getScripts(false) 23 | 24 | await mainScript() 25 | 26 | export {} 27 | -------------------------------------------------------------------------------- /src/cli/tutorial.ts: -------------------------------------------------------------------------------- 1 | // Description: A tutorial to introduce concepts of Simple Scripts 2 | 3 | import { kitMode } from "../core/utils.js" 4 | 5 | export let name = await arg( 6 | `Let's create a script that creates a file from your GitHub profile data. 7 | Please name your script (example: get-profile):` 8 | ) 9 | 10 | await cli("new", name, "--template", "tutorial") 11 | 12 | echo( 13 | chalk`\n🤯 {yellow.italic Type} {green.bold ${name}} {yellow.italic in any directory to run ${name}.${kitMode()}}" 🤯\n` 14 | ) 15 | -------------------------------------------------------------------------------- /src/platform/stackblitz.ts: -------------------------------------------------------------------------------- 1 | await import("./base.js") 2 | 3 | process.env.KIT_EDITOR = env.KIT_EDITOR = `stackblitz` 4 | 5 | global.edit = async (file, dir, line = 0, col = 0) => { 6 | await global.$`open ${file}` 7 | } 8 | 9 | global.trash = async fileOrFiles => { 10 | if (typeof fileOrFiles === "string") { 11 | await $`rm ${fileOrFiles}` 12 | } 13 | 14 | if (Array.isArray(fileOrFiles)) { 15 | for (let file of fileOrFiles) { 16 | await $`rm ${file}` 17 | } 18 | } 19 | } 20 | 21 | export {} 22 | -------------------------------------------------------------------------------- /src/pro/login.ts: -------------------------------------------------------------------------------- 1 | // Name: Login to GitHub 2 | // Description: Authenticate to Enable Features 3 | 4 | import { authenticate } from "../api/kit.js" 5 | 6 | await div( 7 | md(` 8 | # Sign in with GitHub 9 | 10 | ## Sign in to Enable Features 11 | 12 | ### Standard Features 13 | 14 | - Create Gists 15 | 16 | ### Pro Features 17 | 18 | - Debugger 19 | - Sync Scripts to GitHub Repo 20 | - Run Scripts as GitHub Actions 21 | - 22 | `) 23 | ) 24 | 25 | await authenticate() 26 | 27 | await mainScript() 28 | 29 | export {} 30 | -------------------------------------------------------------------------------- /src/main/accessibility.ts: -------------------------------------------------------------------------------- 1 | // Name: Enable Accessibility 2 | // Description: Enable Clipboard and Keyboard Watching 3 | 4 | import { Channel } from "../core/enum.js" 5 | 6 | await div({ 7 | html: md(`## Prompting for Accessibility Permissions 8 | 9 | - Kit needs permission to watch your clipboard and keyboard. This is a one-time prompt. 10 | - Kit.app will quit once it detects the permissions have been granted. 11 | `), 12 | onInit: async () => { 13 | await sendWait(Channel.ENABLE_ACCESSIBILITY, {}) 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /src/globals/index.ts: -------------------------------------------------------------------------------- 1 | export * from './axios.js' 2 | export * from './chalk.js' 3 | export * from './child_process.js' 4 | export * from './crypto.js' 5 | export * from './custom.js' 6 | export * from './download.js' 7 | export * from './execa.js' 8 | export * from './fs-extra.js' 9 | export * from './fs.js' 10 | export * from './globby.js' 11 | export * from './handlebars.js' 12 | export * from './marked.js' 13 | export * from './path.js' 14 | export * from './process.js' 15 | export * from './replace-in-file.js' 16 | export * from './stream.js' 17 | -------------------------------------------------------------------------------- /src/cli/sync-path.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from "fs/promises" 2 | let args = process.argv.slice(2) 3 | 4 | let envPath = args[0] 5 | let contents = await readFile(envPath, "utf8") 6 | 7 | // If contents has a PATH variable, the update it. 8 | // Otherwise, append it to the end of the file. 9 | 10 | if (contents.includes("PATH=")) { 11 | contents = contents.replace( 12 | /^PATH=.*$/gm, 13 | `PATH=${process.env.PATH}` 14 | ) 15 | } else { 16 | contents += ` 17 | PATH=${process.env.PATH} 18 | ` 19 | } 20 | 21 | await writeFile(envPath, contents) 22 | -------------------------------------------------------------------------------- /src/main/showandtell.ts: -------------------------------------------------------------------------------- 1 | // Exclude: true 2 | 3 | setPlaceholder(`Loading "show and tell" discussions...`) 4 | 5 | let showAndTell = `https://scriptkit.com/api/showandtell` 6 | let getChoices = async () => { 7 | let choices = memoryMap.get(showAndTell) 8 | 9 | if (choices) return choices 10 | 11 | return memoryMap 12 | .set(showAndTell, (await get(showAndTell)).data) 13 | .get(showAndTell) 14 | } 15 | 16 | let postChoices = await getChoices() 17 | 18 | let url = await arg(`Pick a post to view:`, postChoices) 19 | 20 | browse(url) 21 | 22 | export {} 23 | -------------------------------------------------------------------------------- /root/themes/nord.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Nord"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #d8dee9; 6 | --color-primary: #88c0d0; 7 | --color-secondary: #4c566a; 8 | --color-background: #2e3440; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/oceanic-next.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Oceanic Next"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #d8dee9; 6 | --color-primary: #6699cc; 7 | --color-secondary: #4f5b66; 8 | --color-background: #1b2b34; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | --mono-font: 'JetBrains Mono', monospace; 12 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 13 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', 'Times New Roman', 'Times', serif; 14 | } -------------------------------------------------------------------------------- /src/cli/sync-path-node.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from "fs/promises" 2 | let args = process.argv.slice(2) 3 | 4 | let envPath = args[0] 5 | let contents = await readFile(envPath, "utf8") 6 | 7 | // If contents has a PATH variable, the update it. 8 | // Otherwise, append it to the end of the file. 9 | 10 | if (contents.includes("PATH=")) { 11 | contents = contents.replace( 12 | /^PATH=.*$/gm, 13 | `PATH=${process.env.PATH}` 14 | ) 15 | } else { 16 | contents += ` 17 | PATH=${process.env.PATH} 18 | ` 19 | } 20 | 21 | await writeFile(envPath, contents) 22 | -------------------------------------------------------------------------------- /root/themes/ayu-dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Ayu Dark"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #b3b1ad; 6 | --color-primary: #e6b450; 7 | --color-secondary: #1a1f29; 8 | --color-background: #0a0e14; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/gruvbox.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Gruvbox"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #ebdbb2; 6 | --color-primary: #fe8019; 7 | --color-secondary: #504945; 8 | --color-background: #282828; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/monokai.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Monokai"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #f8f8f2; 6 | --color-primary: #a6e22e; 7 | --color-secondary: #49483e; 8 | --color-background: #272822; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /src/cli/exists.ts: -------------------------------------------------------------------------------- 1 | let input = await arg("Script name") 2 | 3 | export let exists = (await isBin(kenvPath("bin", input))) 4 | ? chalk`{red.bold ${input}} already exists. Try again:` 5 | : (await isDir(kenvPath("bin", input))) 6 | ? chalk`{red.bold ${input}} exists as group. Enter different name:` 7 | : exec(`command -v ${input}`).stdout 8 | ? chalk`{red.bold ${input}} is a system command. Enter different name:` 9 | : !input.match(/^([a-z]|[0-9]|\-|\/)+$/g) 10 | ? chalk`{red.bold ${input}} can only include lowercase, numbers, and -. Enter different name:` 11 | : true 12 | -------------------------------------------------------------------------------- /src/main/term.ts: -------------------------------------------------------------------------------- 1 | /* 2 | # Terminal 3 | 4 | Run a command in the built-in terminal. 5 | */ 6 | 7 | import { Channel } from "../core/enum.js" 8 | 9 | // Name: Kit Terminal 10 | // Trigger: > 11 | // Pass: true 12 | 13 | setName(``) 14 | 15 | await term({ 16 | command: (arg?.pass as string) || "", 17 | env: process.env, 18 | shortcuts: [ 19 | { 20 | name: "Close", 21 | key: `${cmd}+w`, 22 | bar: "right", 23 | onPress: async () => { 24 | send(Channel.TERM_EXIT, "") 25 | }, 26 | }, 27 | ], 28 | }) 29 | 30 | export {} 31 | -------------------------------------------------------------------------------- /src/setup/kitblitz.ts: -------------------------------------------------------------------------------- 1 | ;(async function () { 2 | await import("../api/global.js") 3 | let $PROJECT = path.resolve(process.cwd()) 4 | let contents = { 5 | installDependencies: true, 6 | startCommand: 7 | "kitbnode ./node_modules/@johnlindquist/kit/setup/kitblitz.js", 8 | env: { 9 | PATH: `${$PROJECT}/bin:${$PROJECT}/node_modules/@johnlindquist/kit/override/stackblitz/bin:/bin:/usr/bin:/usr/local/bin`, 10 | }, 11 | } 12 | let sbrcPath = path.resolve($PROJECT, ".stackblitzrc") 13 | await outputJson(sbrcPath, contents, { spaces: "\t" }) 14 | })() 15 | -------------------------------------------------------------------------------- /root/themes/ayu-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Ayu Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #5c6773; 6 | --color-primary: #ff9940; 7 | --color-secondary: #f0f0f0; 8 | --color-background: #fafafa; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/ayu-mirage.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Ayu Mirage"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #cccac2; 6 | --color-primary: #ffcc66; 7 | --color-secondary: #242936; 8 | --color-background: #1f2430; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/city-lights.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "City Lights"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #b7c5d3; 6 | --color-primary: #68a1f0; 7 | --color-secondary: #28313a; 8 | --color-background: #1d252c; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/github-dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "GitHub Dark"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #c9d1d9; 6 | --color-primary: #58a6ff; 7 | --color-secondary: #30363d; 8 | --color-background: #0d1117; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/monokai-pro.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Monokai Pro"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #f8f8f2; 6 | --color-primary: #a6e22e; 7 | --color-secondary: #49483e; 8 | --color-background: #272822; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/night-owl.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Night Owl"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #d6deeb; 6 | --color-primary: #82aaff; 7 | --color-secondary: #01121f; 8 | --color-background: #011627; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/nord-frost.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Nord Frost"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #d8dee9; 6 | --color-primary: #88c0d0; 7 | --color-secondary: #4c566a; 8 | --color-background: #3b4252; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/nord-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Nord Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #2e3440; 6 | --color-primary: #5e81ac; 7 | --color-secondary: #e5e9f0; 8 | --color-background: #eceff4; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/one-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "One Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #383a42; 6 | --color-primary: #4078f2; 7 | --color-secondary: #a0a1a7; 8 | --color-background: #fafafa; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/palenight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Palenight"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #a6accd; 6 | --color-primary: #82aaff; 7 | --color-secondary: #3a3f58; 8 | --color-background: #292d3e; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/tokyo-night.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Tokyo Night"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #a9b1d6; 6 | --color-primary: #7aa2f7; 7 | --color-secondary: #3b4261; 8 | --color-background: #1a1b26; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/cobalt2-flat.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Cobalt2 Flat"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #ffffff; 6 | --color-primary: #ffc600; 7 | --color-secondary: #0d3a58; 8 | --color-background: #193549; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/cobalt2.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Cobalt2"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #f0f0f0ff; 6 | --color-primary: #ff9d00ff; 7 | --color-secondary: #193a59ff; 8 | --color-background: #002240ff; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/dracula-soft.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Dracula Soft"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #f8f8f2; 6 | --color-primary: #bd93f9; 7 | --color-secondary: #414458; 8 | --color-background: #282a36; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/dracula.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Dracula"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #f8f8f2ff; 6 | --color-primary: #bd93f9ff; 7 | --color-secondary: #4b4e60ff; 8 | --color-background: #282a36ff; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/github-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "GitHub Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #24292e; 6 | --color-primary: #0366d6; 7 | --color-secondary: #e1e4e8; 8 | --color-background: #ffffff; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/gruvbox-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Gruvbox Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #3c3836; 6 | --color-primary: #076678; 7 | --color-secondary: #ebdbb2; 8 | --color-background: #fbf1c7; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/one-dark-pro.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "One Dark Pro"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #abb2bf; 6 | --color-primary: #61afef; 7 | --color-secondary: #3e4451; 8 | --color-background: #282c34; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/panda-syntax.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Panda Syntax"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #e6e6e6; 6 | --color-primary: #ff75b5; 7 | --color-secondary: #292a2b; 8 | --color-background: #1f1f1f; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/synthwave-84.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Synthwave 84"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #f0eff1; 6 | --color-primary: #ff7edb; 7 | --color-secondary: #241b2f; 8 | --color-background: #2b213a; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/atom-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Atom Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #383a42ff; 6 | --color-primary: #4078f2ff; 7 | --color-secondary: #ccccccff; 8 | --color-background: #fbfcfcff; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/catppuccin-frappe.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Catppuccin Frappé"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #c6d0f5; 6 | --color-primary: #ca9ee6; 7 | --color-secondary: #626880; 8 | --color-background: #303446; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", 14 | "Noto Color Emoji"; 15 | --serif-font: "ui-serif", "Georgia", "Cambria", '"Times New Roman"', "Times", "serif"; 16 | } 17 | -------------------------------------------------------------------------------- /root/themes/catppuccin-latte.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Catppuccin Latte"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #4c4f69; 6 | --color-primary: #8839ef; 7 | --color-secondary: #acb0be; 8 | --color-background: #eff1f5; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", 14 | "Noto Color Emoji"; 15 | --serif-font: "ui-serif", "Georgia", "Cambria", '"Times New Roman"', "Times", "serif"; 16 | } 17 | -------------------------------------------------------------------------------- /root/themes/catppuccin-mocha.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Catppuccin Mocha"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #cdd6f4; 6 | --color-primary: #cba6f7; 7 | --color-secondary: #585b70; 8 | --color-background: #1e1e2e; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", 14 | "Noto Color Emoji"; 15 | --serif-font: "ui-serif", "Georgia", "Cambria", '"Times New Roman"', "Times", "serif"; 16 | } 17 | -------------------------------------------------------------------------------- /root/themes/material-darker.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Material Darker"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #eeffff; 6 | --color-primary: #89ddff; 7 | --color-secondary: #3a3f58; 8 | --color-background: #212121; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/material-ocean.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Material Ocean"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #8f93a2; 6 | --color-primary: #89ddff; 7 | --color-secondary: #0f111a; 8 | --color-background: #0f111a; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/night-owl-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Night Owl Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #403f53; 6 | --color-primary: #0c969b; 7 | --color-secondary: #f0f0f0; 8 | --color-background: #fbfbfb; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/nord-aurora.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Nord Aurora"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #d8dee9; 6 | --color-primary: #88c0d0; 7 | --color-secondary: #4c566a; 8 | --color-background: #2e3440; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } 18 | -------------------------------------------------------------------------------- /root/themes/solarized-dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Solarized Dark"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #839496; 6 | --color-primary: #268bd2; 7 | --color-secondary: #073642; 8 | --color-background: #002b36; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/solarized-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Solarized Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #657b83; 6 | --color-primary: #268bd2; 7 | --color-secondary: #eee8d5; 8 | --color-background: #fdf6e3; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/tomorrow-night.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Tomorrow Night"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #c5c8c6; 6 | --color-primary: #81a2be; 7 | --color-secondary: #373b41; 8 | --color-background: #1d1f21; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/winter-is-coming.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Winter is Coming"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #e0e0e0; 6 | --color-primary: #219fd5; 7 | --color-secondary: #33404d; 8 | --color-background: #011627; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/catppuccin-macchiato.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Catppuccin Macchiato"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #cad3f5; 6 | --color-primary: #c6a0f6; 7 | --color-secondary: #5b6078; 8 | --color-background: #24273a; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", 14 | "Noto Color Emoji"; 15 | --serif-font: "ui-serif", "Georgia", "Cambria", '"Times New Roman"', "Times", "serif"; 16 | } 17 | -------------------------------------------------------------------------------- /root/themes/github-dark-dimmed.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "GitHub Dark Dimmed"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #adbac7; 6 | --color-primary: #539bf5; 7 | --color-secondary: #373e47; 8 | --color-background: #22272e; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/material-lighter.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Material Lighter"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #80cbc4; 6 | --color-primary: #39adb5; 7 | --color-secondary: #cceae7; 8 | --color-background: #fafafa; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/material-palenight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Material Palenight"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #a6accd; 6 | --color-primary: #c792ea; 7 | --color-secondary: #4e5579; 8 | --color-background: #292d3e; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/tokyo-night-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Tokyo Night Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #343b58; 6 | --color-primary: #34548a; 7 | --color-secondary: #d5d6db; 8 | --color-background: #e1e2e7; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/tokyo-night-storm.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Tokyo Night Storm"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #a9b1d6; 6 | --color-primary: #7aa2f7; 7 | --color-secondary: #292e42; 8 | --color-background: #24283b; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /src/run/stackblitz.ts: -------------------------------------------------------------------------------- 1 | process.env.KIT_TARGET = "stackblitz" 2 | 3 | import path from "path" 4 | 5 | process.env.KIT = path.resolve( 6 | "node_modules", 7 | "@johnlindquist", 8 | "kit" 9 | ) 10 | process.env.KENV = path.resolve() 11 | 12 | import { configEnv } from "../core/utils.js" 13 | 14 | await import("../api/global.js") 15 | await import("../api/kit.js") 16 | await import("../api/lib.js") 17 | 18 | await import(`../platform/stackblitz.js`) 19 | 20 | configEnv() 21 | 22 | await import("../target/terminal.js") 23 | let { runCli } = await import("../cli/kit.js") 24 | 25 | await runCli() 26 | -------------------------------------------------------------------------------- /root/themes/material-deep-ocean.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Material Deep Ocean"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #8f93a2; 6 | --color-primary: #84ffff; 7 | --color-secondary: #1e2a35; 8 | --color-background: #0f111a; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/night-owl-no-italics.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Night Owl No Italics"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #d6deeb; 6 | --color-primary: #82aaff; 7 | --color-secondary: #01121f; 8 | --color-background: #011627; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/synthwave-84-alpha.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Synthwave 84 Alpha"; 3 | --appearance: dark; 4 | --opacity: 0.85; 5 | --color-text: #f0eff1; 6 | --color-primary: #ff7edb; 7 | --color-secondary: #241b2f; 8 | --color-background: #2b213aaa; 9 | --ui-bg-opacity: 0.5; 10 | --ui-border-opacity: 0.7; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/synthwave-84-no-glow.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Synthwave 84 No Glow"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #f0eff1; 6 | --color-primary: #ff7edb; 7 | --color-secondary: #241b2f; 8 | --color-background: #262335; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /src/cli/settings.ts: -------------------------------------------------------------------------------- 1 | // Description: Kit.app Settings 2 | 3 | import { 4 | escapeShortcut, 5 | closeShortcut, 6 | cmd, 7 | } from "../core/utils.js" 8 | 9 | let kenvEnvPath = kenvPath(".env") 10 | await editor({ 11 | value: await readFile(kenvEnvPath, "utf-8"), 12 | language: "properties", 13 | shortcuts: [ 14 | escapeShortcut, 15 | closeShortcut, 16 | { 17 | name: "Save", 18 | key: `${cmd}+s`, 19 | onPress: async input => { 20 | await writeFile(kenvEnvPath, input) 21 | }, 22 | bar: "right", 23 | }, 24 | ], 25 | }) 26 | 27 | export {} 28 | -------------------------------------------------------------------------------- /root/script: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ../../ of this script 3 | 4 | KIT=$(cd "$(dirname ${BASH_SOURCE[0]})" &> /dev/null && pwd) 5 | KIT_NODE_PATH="${KIT_NODE_PATH:=$($KIT/node_modules/.bin/pnpm node -p "process.execPath" 2>/dev/null || pnpm node -p "process.execPath" 2>/dev/null || node -p "process.execPath" 2>/dev/null)}" 6 | 7 | if [ -z "$KIT_NODE_PATH" ]; then 8 | echo "Error: Node.js not found in PATH. Provide an KIT_NODE_PATH in your environment." >&2 9 | exit 1 10 | fi 11 | 12 | NODE_NO_WARNINGS=1 \ 13 | "$KIT_NODE_PATH" \ 14 | --loader "file://$KIT/build/loader.js" \ 15 | "$KIT/run/terminal.js" \ 16 | "$@" -------------------------------------------------------------------------------- /root/themes/github-light-colorblind.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "GitHub Light Colorblind"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #24292f; 6 | --color-primary: #0969da; 7 | --color-secondary: #d0d7de; 8 | --color-background: #ffffff; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/night-owl-light-italic.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Night Owl Light Italic"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #403f53; 6 | --color-primary: #0c969b; 7 | --color-secondary: #f0f0f0; 8 | --color-background: #fbfbfb; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/winter-is-coming-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Winter is Coming Light"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #2e3440; 6 | --color-primary: #0066b8; 7 | --color-secondary: #e4e4e4; 8 | --color-background: #ffffff; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /src/cli/more-info.ts: -------------------------------------------------------------------------------- 1 | let file = JSON.parse( 2 | await readFile(kenvPath("package.json"), { 3 | encoding: "utf8", 4 | }) 5 | ) 6 | 7 | let site = await arg( 8 | chalk`Which package site do you want to visit?`, 9 | [ 10 | ...Object.keys(file?.dependencies || []), 11 | ...Object.keys(file?.devDependencies || []), 12 | ].map(name => ({ 13 | name, 14 | value: `https://npmjs.com/package/${name}`, 15 | description: `https://npmjs.com/package/${name}`, 16 | })) 17 | ) 18 | 19 | // console.log(`Opening ${site}`) 20 | 21 | await browse(site) 22 | 23 | exit() 24 | 25 | export {} 26 | -------------------------------------------------------------------------------- /src/cli/open-at-login.ts: -------------------------------------------------------------------------------- 1 | // Description: Toggle Open at Login 2 | 3 | import { getEnvVar, setEnvVar } from "../api/kit.js" 4 | 5 | let openAtLogin = await getEnvVar( 6 | "KIT_OPEN_AT_LOGIN", 7 | "true" 8 | ) 9 | 10 | let placeholder = `"Open at login" is ${ 11 | openAtLogin ? "en" : "dis" 12 | }abled` 13 | 14 | let toggle = await arg(placeholder, [ 15 | { 16 | name: "Enable", 17 | value: true, 18 | }, 19 | { 20 | name: "Disable", 21 | value: false, 22 | }, 23 | ]) 24 | 25 | await setEnvVar( 26 | "KIT_OPEN_AT_LOGIN", 27 | toggle ? "true" : "false" 28 | ) 29 | 30 | export {} 31 | -------------------------------------------------------------------------------- /root/themes/github-dark-high-contrast.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "GitHub Dark High Contrast"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #f0f3f6; 6 | --color-primary: #71b7ff; 7 | --color-secondary: #3b434b; 8 | --color-background: #0a0c10; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /src/config/set-default-display.ts: -------------------------------------------------------------------------------- 1 | let screens = await getScreens() 2 | 3 | let widgets = [] 4 | 5 | for (let s of screens) { 6 | let w = await widget( 7 | md(`# ${s.id}`), 8 | { 9 | x: s.workArea.x, 10 | y: s.workArea.y, 11 | transparent: true, 12 | } 13 | ) 14 | widgets.push(w) 15 | } 16 | 17 | let displayId = await arg( 18 | "Select default display", 19 | screens.map(s => s.id.toString()) 20 | ) 21 | 22 | await cli("set-env-var", "KIT_DISPLAY", displayId) 23 | 24 | for (let w of widgets) { 25 | await w.close() 26 | } 27 | 28 | export {} 29 | -------------------------------------------------------------------------------- /root/themes/night-owl-light-no-italics.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Night Owl Light No Italics"; 3 | --appearance: light; 4 | --opacity: 0.9; 5 | --color-text: #403f53; 6 | --color-primary: #0c969b; 7 | --color-secondary: #f0f0f0; 8 | --color-background: #fbfbfb; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /root/themes/solarized-dark-high-contrast.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Solarized Dark High Contrast"; 3 | --appearance: dark; 4 | --opacity: 0.9; 5 | --color-text: #fdf6e3; 6 | --color-primary: #268bd2; 7 | --color-secondary: #073642; 8 | --color-background: #002b36; 9 | --ui-bg-opacity: 0.6; 10 | --ui-border-opacity: 0.6; 11 | 12 | --mono-font: JetBrains Mono; 13 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 14 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 16 | 'serif'; 17 | } -------------------------------------------------------------------------------- /src/setup/create-env.ts: -------------------------------------------------------------------------------- 1 | // Check if .env already exists 2 | const envPath = kenvPath(".env") 3 | const alreadyExists = await pathExists(envPath) 4 | if (!alreadyExists) { 5 | let envTemplatePath = kitPath("templates", "env", "template.env") 6 | 7 | let envTemplate = await readFile(envTemplatePath, "utf8") 8 | 9 | let envTemplateCompiler = compile(envTemplate) 10 | let compiledEnvTemplate = envTemplateCompiler({ 11 | ...process.env, 12 | KIT_MAIN_SHORTCUT: process.platform === "win32" ? "ctrl ;" : "cmd ;" 13 | }) 14 | 15 | await writeFile(kenvPath(".env"), compiledEnvTemplate) 16 | 17 | } 18 | 19 | export {} -------------------------------------------------------------------------------- /src/core/color.ts: -------------------------------------------------------------------------------- 1 | import colors from "color-name" 2 | 3 | export const toHex = (hexOrRgbOrName: string) => { 4 | if (hexOrRgbOrName.includes(",")) { 5 | const [r, g, b] = hexOrRgbOrName 6 | .split(",") 7 | .map(c => parseInt(c, 10)) 8 | 9 | const hex = `#${r.toString(16)}${g.toString( 10 | 16 11 | )}${b.toString(16)}` 12 | // fill zeros 13 | return hex.replace( 14 | /#([0-9a-f])([0-9a-f])([0-9a-f])$/i, 15 | "#$1$1$2$2$3$3" 16 | ) 17 | } 18 | 19 | if (colors[hexOrRgbOrName]) 20 | return colors[hexOrRgbOrName].join(",") 21 | 22 | return hexOrRgbOrName 23 | } 24 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import "./globals.d.ts" 2 | import type { AppApi } from "./kitapp.ts" 3 | import type { KitApi, Run } from "./kit.ts" 4 | import type { PackagesApi } from "./packages.ts" 5 | import type { PlatformApi } from "./platform.ts" 6 | import type { ProAPI } from "./pro.ts" 7 | 8 | export type GlobalApi = 9 | KitApi & 10 | PackagesApi & 11 | PlatformApi & 12 | AppApi & 13 | ProAPI 14 | 15 | declare global { 16 | var kit: GlobalApi & Run 17 | interface Global extends GlobalApi {} 18 | } 19 | 20 | export type * from "./core.d.ts" 21 | export type * from "../core/utils.d.ts" 22 | 23 | export default kit 24 | -------------------------------------------------------------------------------- /src/cli/share-script-as-kit-link.ts: -------------------------------------------------------------------------------- 1 | //Menu: Share Script as kit:// link 2 | //Description: Create a base64 encoded link to share 3 | 4 | let { filePath, command } = await selectScript( 5 | `Share which script?` 6 | ) 7 | 8 | let contents = await readFile(filePath, "utf8") 9 | let c = Buffer.from(contents).toString("base64url") 10 | 11 | let link = `kit://snippet?name=${command}&content=${c}` 12 | 13 | copy(link) 14 | 15 | let message = `Copied kit:// share link to clipboard` 16 | 17 | setAlwaysOnTop(true) 18 | 19 | await div( 20 | await highlight(`## ${message} 21 | 22 | [${link}](${link}) 23 | `) 24 | ) 25 | 26 | export {} 27 | -------------------------------------------------------------------------------- /src/cli/join.ts: -------------------------------------------------------------------------------- 1 | // Description: Subscribe to the Script Kit Newsletter 2 | import { getMainScriptPath } from "../core/utils.js" 3 | setChoices([]) 4 | let email = await arg( 5 | "Enter e-mail to join newsletter:", 6 | md(` 7 | # Script Kit Newletters include: 8 | 9 | * Tips for writing scripts 10 | * Community script highlights 11 | * Automation ideas 12 | * Upcoming features 13 | `) 14 | ) 15 | await post(`https://scriptkit.com/api/subscribe`, { 16 | email, 17 | }) 18 | await div( 19 | md(`## Thanks! Make sure to confirm in your mail app 😇`) 20 | ) 21 | if (process.env.KIT_CONTEXT === "app") { 22 | await run(getMainScriptPath()) 23 | } 24 | -------------------------------------------------------------------------------- /src/setup/update-examples.ts: -------------------------------------------------------------------------------- 1 | import { KIT_FIRST_PATH } from '../core/utils.js' 2 | 3 | let examplesDir = kenvPath('kenvs', 'examples') 4 | if (await isDir(examplesDir)) { 5 | await exec(`git stash`, { 6 | cwd: examplesDir 7 | }) 8 | let { stdout } = await exec(`git pull`, { 9 | cwd: examplesDir 10 | }) 11 | 12 | if (!stdout.includes('Already up to date.')) { 13 | try { 14 | await exec(`pnpm i`, { 15 | cwd: kenvPath('kenvs', 'examples'), 16 | env: { 17 | ...global.env, 18 | PATH: KIT_FIRST_PATH 19 | } 20 | }) 21 | } catch (error) {} 22 | } 23 | } 24 | 25 | export {} 26 | -------------------------------------------------------------------------------- /root/themes/script-kit-dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Script Kit Dark"; 3 | --appearance: dark; 4 | --opacity-mac: 0.25; 5 | --opacity-win: 0.5; 6 | --opacity-other: 0.5; 7 | --color-text: #ffffffee; 8 | --color-primary: #fbbf24ee; 9 | --color-secondary: #ffffff; 10 | --color-background: #0f0f0f; 11 | --ui-bg-opacity: 0.08; 12 | --ui-border-opacity: 0.1; 13 | --mono-font: JetBrains Mono; 14 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 15 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'; 16 | } -------------------------------------------------------------------------------- /src/cli/new-from-template.ts: -------------------------------------------------------------------------------- 1 | // Description: Creates a new empty script you can invoke from the terminal 2 | 3 | import { kitMode } from "../core/utils.js" 4 | import { ensureTemplates } from "./lib/utils.js" 5 | 6 | await ensureTemplates() 7 | 8 | let kenvTemplatesPath = kenvPath("templates") 9 | let templates = await readdir(kenvTemplatesPath) 10 | let template = await arg( 11 | "Select a template", 12 | templates 13 | .filter(t => t.endsWith(kitMode())) 14 | .map(t => { 15 | let ext = path.extname(t) 16 | return t.replace(new RegExp(`${ext}$`), "") 17 | }) 18 | ) 19 | 20 | await cli("new", "--template", template) 21 | 22 | export {} 23 | -------------------------------------------------------------------------------- /src/cli/npm.ts: -------------------------------------------------------------------------------- 1 | import { adjustPackageName } from "../core/utils.js" 2 | import { appInstallMultiple } from "../target/app.js" 3 | 4 | delete flag.trigger 5 | delete flag.force 6 | 7 | let missingPackages = [ 8 | ...new Set( 9 | args.reduce( 10 | (acc, pkg) => ( 11 | !pkg.startsWith("node:") && 12 | acc.push(adjustPackageName(pkg)), 13 | acc 14 | ), 15 | [] 16 | ) 17 | ), 18 | ] 19 | args = [] 20 | 21 | if (missingPackages.length > 1) { 22 | await appInstallMultiple(missingPackages) 23 | } else if (missingPackages.length === 1) { 24 | await installMissingPackage(missingPackages[0]) 25 | } 26 | 27 | export {} 28 | -------------------------------------------------------------------------------- /src/cli/run.ts: -------------------------------------------------------------------------------- 1 | // Description: Run the selected script 2 | import { isScriptlet, isSnippet, run } from "../core/utils.js" 3 | import { runScriptlet } from "../main/scriptlet.js" 4 | import type { Script } from "../types/core.js" 5 | 6 | let script = await selectScript("Which script do you want to run?", true) 7 | let runScript = async (script) => { 8 | if (isScriptlet(script)) { 9 | console.log("Running scriptlet", script) 10 | updateArgs(args) 11 | return await runScriptlet(script, [], flag) 12 | } 13 | if (isSnippet(script)) { 14 | console.log(script.value) 15 | return 16 | } 17 | return await run(script.filePath) 18 | } 19 | await runScript(script) 20 | -------------------------------------------------------------------------------- /src/help/info-full-disk-access.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | import { cmd } from "../core/utils.js" 3 | 4 | let html = md(` 5 | ## Browsing Files Requires Full Disk Access 6 | 7 | Please verify "Full Disk Access" in your security preferences. 8 | 9 | Once enabled, quit Kit.app from the menubar and restart it. 10 | `) 11 | 12 | await div({ 13 | html, 14 | enter: "Back to Main", 15 | shortcuts: [ 16 | { 17 | name: "Quit", 18 | key: `${cmd}+q`, 19 | bar: "right", 20 | onPress: async () => { 21 | send(Channel.QUIT_APP) 22 | }, 23 | }, 24 | ], 25 | }) 26 | 27 | await mainScript() 28 | 29 | export {} 30 | -------------------------------------------------------------------------------- /src/cli/download-md.ts: -------------------------------------------------------------------------------- 1 | let downloadMarkdown = async (fileName: string) => { 2 | try { 3 | let url = `https://raw.githubusercontent.com/johnlindquist/kit-docs/main/${fileName}` 4 | console.log(`Downloading ${url}...`) 5 | let buffer = await download(url, undefined, { 6 | rejectUnauthorized: false, 7 | }) 8 | await writeFile(kitPath(fileName), buffer) 9 | } catch (error) { 10 | log(`Error downloading ${fileName}`) 11 | } 12 | } 13 | 14 | await downloadMarkdown("API.md") 15 | await downloadMarkdown("GUIDE.md") 16 | await downloadMarkdown("KIT.md") 17 | await downloadMarkdown("TIPS.md") 18 | await downloadMarkdown("SPONSOR.md") 19 | 20 | export {} 21 | -------------------------------------------------------------------------------- /root/themes/script-kit-light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --name: "Script Kit Light"; 3 | --appearance: light; 4 | --opacity-mac: 0.5; 5 | --opacity-win: 0.9; 6 | --opacity-other: 0.9; 7 | --color-text: #2C2C2C; 8 | --color-primary: #2F86D3; 9 | --color-secondary: #2F86D3; 10 | --color-background: #ffffff; 11 | --ui-bg-opacity: 0.15; 12 | --ui-border-opacity: 0.15; 13 | 14 | --mono-font: JetBrains Mono; 15 | --sans-font: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 16 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 17 | --serif-font: 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 18 | 'serif'; 19 | } -------------------------------------------------------------------------------- /src/cli/remove.ts: -------------------------------------------------------------------------------- 1 | // Description: Remove a script 2 | 3 | import { refreshScripts } from "../core/db.js" 4 | import { trashScript } from "../core/utils.js" 5 | 6 | let script = await selectScript(`Remove a script:`) 7 | 8 | let { command, filePath } = script 9 | 10 | let confirm = 11 | global?.flag?.confirm || 12 | (await arg(`Remove ${command}?`, [ 13 | { info: true, name: filePath }, 14 | { name: "[N]o, cancel.", value: false }, 15 | { name: `[Y]es, remove ${command}`, value: true }, 16 | ])) 17 | 18 | if (confirm) { 19 | await trashScript(script) 20 | await refreshScripts() 21 | } 22 | 23 | if (process.env.KIT_CONTEXT === "app") { 24 | await mainScript() 25 | } 26 | export {} 27 | -------------------------------------------------------------------------------- /scripts/init.js: -------------------------------------------------------------------------------- 1 | let removeSrc = await isDir(kitPath("src")); 2 | if (removeSrc) { 3 | await trash([kitPath("src")]); 4 | } 5 | let kitScriptsPath = kitPath("scripts"); 6 | cp(kenvPath("scripts/*"), kitScriptsPath); 7 | let scripts = await readdir(kitScriptsPath); 8 | let scriptImports = scripts 9 | .filter(s => s.endsWith(".js")) 10 | .map(s => `\tawait import("./scripts/${s}")`) 11 | .join("\n"); 12 | let keepFn = `$1 13 | 14 | async function keep(){ 15 | ${scriptImports} 16 | } 17 | `; 18 | let runFile = kitPath("run.js"); 19 | let contents = await readFile(runFile, "utf-8"); 20 | let replaced = contents.replace(/(codegen).*/gs, keepFn); 21 | await writeFile(runFile, replaced); 22 | export {}; 23 | -------------------------------------------------------------------------------- /src/help/join.ts: -------------------------------------------------------------------------------- 1 | // Description: Subscribe to the Script Kit Newsletter 2 | 3 | import { getMainScriptPath } from "../core/utils.js" 4 | 5 | setChoices([]) 6 | 7 | let email = await arg( 8 | "Enter e-mail to join newsletter:", 9 | await highlight(` 10 | ## Script Kit Newletters include: 11 | * Tips for writing scripts 12 | * Community script highlights 13 | * Automation ideas 14 | * Upcoming features 15 | `) 16 | ) 17 | 18 | await post(`https://scriptkit.com/api/subscribe`, { 19 | email, 20 | }) 21 | 22 | await div( 23 | md(`## Thanks! Make sure to confirm in your mail app 😇`) 24 | ) 25 | 26 | if (process.env.KIT_CONTEXT === "app") { 27 | await run(getMainScriptPath()) 28 | } 29 | 30 | export {} 31 | -------------------------------------------------------------------------------- /src/cli/view-docs.ts: -------------------------------------------------------------------------------- 1 | // Name: Search Docs 2 | // Description: Type to Search Docs 3 | 4 | // Log: false 5 | export {} 6 | 7 | let docsDir = home(".kit-docs", "docs") 8 | 9 | if (await pathExists(docsDir)) { 10 | let docsFiles = await readdir(docsDir) 11 | 12 | await arg( 13 | "Docs", 14 | docsFiles.map(file => ({ 15 | name: file, 16 | preview: async () => { 17 | let contents = await readFile( 18 | kitPath("docs", file), 19 | "utf-8" 20 | ) 21 | 22 | return global.highlightMd(contents) 23 | }, 24 | })) 25 | ) 26 | } else { 27 | await arg({ 28 | placeholder: `Can't find docs dir`, 29 | hint: `Todo: prompt to clone docs`, 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/setup/cache-grouped-scripts.ts: -------------------------------------------------------------------------------- 1 | // Description: Clear Timestamps 2 | 3 | import type { Script } from '../types/core' 4 | import { getGroupedScripts, processScriptPreview, scriptFlags } from '../api/kit.js' 5 | import { Channel } from '../core/enum.js' 6 | import { formatChoices } from '../core/utils.js' 7 | 8 | let groupedScripts = await getGroupedScripts() 9 | let scripts = formatChoices(groupedScripts) 10 | let firstScript = scripts.find((script) => !script.skip) 11 | let preview = '' 12 | try { 13 | preview = (await processScriptPreview(firstScript as unknown as Script)()) as string 14 | } catch {} 15 | 16 | process.send({ 17 | channel: Channel.CACHE_SCRIPTS, 18 | scripts, 19 | preview, 20 | scriptFlags 21 | }) 22 | -------------------------------------------------------------------------------- /src/scripts/init.ts: -------------------------------------------------------------------------------- 1 | let removeSrc = await isDir(kitPath("src")) 2 | if (removeSrc) { 3 | await trash([kitPath("src")]) 4 | } 5 | 6 | let kitScriptsPath = kitPath("scripts") 7 | 8 | cp(kenvPath("scripts/*"), kitScriptsPath) 9 | let scripts = await readdir(kitScriptsPath) 10 | 11 | let scriptImports = scripts 12 | .filter(s => s.endsWith(".js")) 13 | .map(s => `\tawait import("./scripts/${s}")`) 14 | .join("\n") 15 | 16 | let keepFn = `$1 17 | 18 | async function keep(){ 19 | ${scriptImports} 20 | } 21 | ` 22 | 23 | let runFile = kitPath("run.js") 24 | let contents = await readFile(runFile, "utf-8") 25 | let replaced = contents.replace(/(codegen).*/gs, keepFn) 26 | await writeFile(runFile, replaced) 27 | 28 | export {} 29 | -------------------------------------------------------------------------------- /src/globals/path.ts: -------------------------------------------------------------------------------- 1 | import _path from "node:path" 2 | export let path = ((global as any).path = _path); 3 | export let resolve = (global.resolve = _path.resolve) 4 | export let join = (global.join = _path.join) 5 | export let dirname = (global.dirname = _path.dirname) 6 | export let basename = (global.basename = _path.basename) 7 | export let extname = (global.extname = _path.extname) 8 | export let relative = (global.relative = _path.relative) 9 | export let normalize = (global.normalize = _path.normalize) 10 | export let isAbsolute = (global.isAbsolute = _path.isAbsolute) 11 | export let sep = (global.sep = _path.sep) 12 | export let delimiter = (global.delimiter = _path.delimiter) 13 | export let parse = (global.parse = _path.parse) 14 | -------------------------------------------------------------------------------- /src/pro/sponsor-check.ts: -------------------------------------------------------------------------------- 1 | // Name: Sponsor Check 2 | 3 | let check = await arg("Sponsor Check") 4 | 5 | let sponsorUrl = `https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205` 6 | try { 7 | sponsorUrl = ( 8 | await readFile( 9 | kitPath("data", "sponsor-url.txt"), 10 | "utf-8" 11 | ) 12 | ).trim() 13 | } catch (error) { 14 | warn(`Failed to read sponsor-url.txt`) 15 | } 16 | 17 | if (check === "success") { 18 | await div(`# You are a Sponsor! Thank you!`) 19 | } else { 20 | await div(`# You are not currently a Sponsor... 21 | 22 | Please go to [https://github.com/sponsors/johnlindquist](${sponsorUrl}) to become a sponsor to unlock this feature. 23 | `) 24 | } 25 | 26 | export {} 27 | -------------------------------------------------------------------------------- /src/main/docs.ts: -------------------------------------------------------------------------------- 1 | // Name: Docs: API, Guide, Tips, Community, Announcements 2 | // Description: Script Kit Docs 3 | // Enter: Open Docs 4 | // Keyword: docs 5 | // Pass: true 6 | 7 | delete arg?.pass 8 | delete arg?.keyword 9 | setInput("") 10 | 11 | onTab("API", async (input = "") => { 12 | await main("api", "--input", input) 13 | }) 14 | 15 | onTab("Guide", async (input = "") => { 16 | await main("guide", "--input", input) 17 | }) 18 | 19 | onTab("Tips", async (input = "") => { 20 | await main("tips", "--input", input) 21 | }) 22 | 23 | onTab("Community Scripts", async (input = "") => { 24 | await main("community", "--input", input) 25 | }) 26 | 27 | onTab("Announcements", async (input = "") => { 28 | await main("announcements", "--input", input) 29 | }) 30 | -------------------------------------------------------------------------------- /src/globals/execa.ts: -------------------------------------------------------------------------------- 1 | import * as all from "execa" 2 | import type { Options } from "execa" 3 | 4 | export let execa = all.execa 5 | global.execa = execa 6 | 7 | export let execaSync = all.execaSync 8 | global.execaSync = execaSync 9 | 10 | export let execaCommand = all.execaCommand 11 | global.execaCommand = execaCommand 12 | global.exec = ((command: string, options: Options = { shell: true, cwd: process.cwd() }) => { 13 | return execaCommand(command, options) 14 | }) as unknown as typeof execaCommand 15 | export let exec = global.exec 16 | 17 | export let execaCommandSync = all.execaCommandSync 18 | global.execaCommandSync = execaCommandSync 19 | 20 | export let execaNode = all.execaNode 21 | global.execaNode = execaNode 22 | 23 | global.$ = all.$ 24 | export let $ = global.$ -------------------------------------------------------------------------------- /src/main/kit-windows.ts: -------------------------------------------------------------------------------- 1 | // Name: Focus Kit Window 2 | // Description: Focus a Kit Window 3 | // Keyword: kw 4 | // Enter: Focus 5 | 6 | import type { Choice } from "../types/core" 7 | 8 | let windows = await getKitWindows() 9 | windows = windows.filter(w => !w.isFocused) 10 | 11 | if (!windows.length) { 12 | await div( 13 | md(`# No Kit Windows Found... 14 | 15 | Try launching a script with a "widget", then run this again. 16 | 17 | ~~~js 18 | await widget(md(\`# Hello world\`), { 19 | title: "My Widget", 20 | }) 21 | ~~~ 22 | `) 23 | ) 24 | } else { 25 | let id = await arg( 26 | { 27 | placeholder: "Focus Kit Window", 28 | enter: "Focus", 29 | }, 30 | windows as Choice[] 31 | ) 32 | 33 | await focusKitWindow(id) 34 | } 35 | -------------------------------------------------------------------------------- /src/cli/remove-env-var.ts: -------------------------------------------------------------------------------- 1 | import { kitDotEnvPath } from "../core/utils.js" 2 | 3 | let envKey = await arg("env key:") 4 | let envFile = kenvPath(".env") 5 | 6 | let removeEnv = async envKey => { 7 | let regex = new RegExp("^" + envKey + "=.*$", "gm") 8 | sed("-i", regex, "", envFile) 9 | delete env[envKey] 10 | delete process.env[envKey] 11 | } 12 | 13 | let dotEnvPath = kitDotEnvPath() 14 | await ensureFile(dotEnvPath) 15 | let contents = await readFile(dotEnvPath, "utf-8") 16 | let exists = contents.match( 17 | new RegExp("^" + envKey + "=.*$", "gm") 18 | ) 19 | 20 | if (exists) { 21 | await removeEnv(envKey) 22 | } else { 23 | global.log( 24 | chalk`{yellow.bold ${envKey}} not found in ${kenvPath( 25 | ".env" 26 | )}` 27 | ) 28 | } 29 | export {} 30 | -------------------------------------------------------------------------------- /src/setup/downloads.ts: -------------------------------------------------------------------------------- 1 | // Description: Downloads the latest docs and hot 2 | // This is run by the app. 3 | 4 | try { 5 | await ensureDir(kitPath("data")) 6 | await run(kitPath("hot", "download-hot.js")) 7 | await run(kitPath("emoji", "download-emoji.js")) 8 | if (!arg.dev) await run(kitPath("cli", "download-md.js")) 9 | if (!arg.dev) { 10 | await run(kitPath("setup", "update-examples.js")) 11 | } 12 | await run(kitPath("setup", "clone-sponsors.js")) 13 | let response = await get( 14 | `https://scriptkit.com/api/get-sponsor-url` 15 | ) 16 | if (response.data) { 17 | await writeFile( 18 | kitPath("data", "sponsor-url.txt"), 19 | response.data 20 | ) 21 | } 22 | } catch { 23 | console.warn(`Failed to download data`) 24 | } 25 | 26 | export {} 27 | -------------------------------------------------------------------------------- /src/lib/text.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | global.getSelectedText = async () => { 4 | await global.hide() 5 | 6 | await sendWait(Channel.KEYBOARD_COPY) 7 | 8 | const result = await global.paste() 9 | 10 | if (result?.replace(/[\r\n]/g, "") === "") { 11 | return "" 12 | } 13 | 14 | return result 15 | } 16 | 17 | /** 18 | @param text - A String to Paste at the Cursor 19 | @example 20 | ``` 21 | await setSelectedText(`Script Kit is awesome!`) 22 | ``` 23 | */ 24 | 25 | global.setSelectedText = async (text = "", hide = true) => { 26 | if (hide) await global.hide() 27 | 28 | return await sendWait(Channel.SET_SELECTED_TEXT, { 29 | text, 30 | hide, 31 | }) 32 | } 33 | 34 | global.cutText = () => sendWait(Channel.CUT_TEXT) 35 | 36 | export {} 37 | -------------------------------------------------------------------------------- /src/api/packages/shelljs.ts: -------------------------------------------------------------------------------- 1 | let { default: shelljs }: any = await import("shelljs") 2 | const { 3 | cp, 4 | chmod, 5 | echo, 6 | exit, 7 | grep, 8 | ln, 9 | ls, 10 | mkdir, 11 | mv, 12 | sed, 13 | pwd, 14 | tempdir, 15 | test, 16 | which, 17 | cd, 18 | } = shelljs 19 | 20 | global.cp = cp 21 | global.chmod = chmod 22 | global.echo = echo 23 | global.exit = (code?: number) => { 24 | if (process?.env?.KIT_TARGET === "app-prompt") { 25 | global?.send("BEFORE_EXIT" as any) 26 | } 27 | process.exit(code) 28 | } 29 | global.grep = grep 30 | global.ln = ln 31 | global.ls = ls 32 | global.mkdir = mkdir 33 | global.mv = mv 34 | global.sed = sed 35 | global.tempdir = tempdir 36 | global.test = test 37 | global.which = which 38 | global.pwd = pwd 39 | global.cd = cd 40 | 41 | export {} 42 | -------------------------------------------------------------------------------- /src/core/test-utils.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path" 2 | import { ensureDir, pathExists, readFile, writeFile } from "../../src/globals/index" 3 | 4 | export async function loadPreviousResults(filename: string = getBenchmarkFilename()) { 5 | if (!await pathExists(filename)) return {} 6 | return JSON.parse(await readFile(filename, 'utf8')) 7 | } 8 | 9 | export async function saveResults(results: any, filename: string = getBenchmarkFilename()) { 10 | await ensureDir(path.dirname(filename)) 11 | await writeFile(filename, JSON.stringify(results, null, 2), 'utf8') 12 | } 13 | 14 | export function getBenchmarkFilename() { 15 | return path.join(process.env.HOME, ".benchmarks", `${ 16 | new URL(import.meta.url).pathname.split('/').pop()?.replace(/\.test\.ts$/, '-benchmark.json') 17 | }`) 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/audio.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | global.playAudioFile = async ( 4 | filePath, 5 | options = { 6 | rate: 1.0, 7 | } 8 | ) => { 9 | return await sendWait( 10 | Channel.PLAY_AUDIO, 11 | { 12 | filePath, 13 | ...options, 14 | }, 15 | 0 16 | ) 17 | } 18 | 19 | global.stopAudioFile = async () => { 20 | return await sendWait(Channel.STOP_AUDIO, undefined, 0) 21 | } 22 | 23 | global.say = async ( 24 | text, 25 | options = { 26 | rate: 1.2, 27 | name: "Daniel", 28 | } 29 | ) => { 30 | await sendWait( 31 | Channel.SPEAK_TEXT, 32 | { 33 | text, 34 | ...options, 35 | }, 36 | 0 37 | ) 38 | 39 | return text 40 | } 41 | 42 | global.beep = async () => { 43 | await sendWait(Channel.BEEP) 44 | } 45 | 46 | export {} 47 | -------------------------------------------------------------------------------- /src/cli/switch-to-ts.ts: -------------------------------------------------------------------------------- 1 | // Description: Switch to TypeScript Mode 2 | 3 | await global.cli("set-env-var", "KIT_MODE", "ts") 4 | process.env.KIT_MODE = "ts" 5 | 6 | let tsConfigPath = kenvPath("tsconfig.json") 7 | let tsConfigExists = await pathExists(tsConfigPath) 8 | let tsDefaultTemplatePath = kenvPath( 9 | "templates", 10 | "default.ts" 11 | ) 12 | let tsDefaultTemplateExists = await pathExists( 13 | tsDefaultTemplatePath 14 | ) 15 | 16 | if (!tsConfigExists) { 17 | await copyFile( 18 | kitPath("templates", "config", "tsconfig.json"), 19 | tsConfigPath 20 | ) 21 | } 22 | 23 | if (!tsDefaultTemplateExists) { 24 | await ensureDir(kenvPath("templates")) 25 | await copyFile( 26 | kitPath("templates", "scripts", "default.ts"), 27 | tsDefaultTemplatePath 28 | ) 29 | } 30 | await mainScript() 31 | 32 | export {} 33 | -------------------------------------------------------------------------------- /src/run/app.ts: -------------------------------------------------------------------------------- 1 | process.env.KIT_TARGET = "app" 2 | 3 | import { Channel } from "../core/enum.js" 4 | import { configEnv, run } from "../core/utils.js" 5 | import os from "node:os" 6 | 7 | await import("../api/global.js") 8 | await import("../api/kit.js") 9 | await import("../api/pro.js") 10 | await import("../api/lib.js") 11 | await import("../platform/base.js") 12 | 13 | let platform = os.platform() 14 | try { 15 | await import(`../platform/${platform}.js`) 16 | } catch (error) { 17 | // console.log(`No ./platform/${platform}.js`) 18 | } 19 | await import("../target/app.js") 20 | 21 | configEnv() 22 | process.title = "Kit Idle - App" 23 | let script = await arg("Path to script:") 24 | process.title = path.basename(script) 25 | 26 | process.once("beforeExit", () => { 27 | send(Channel.BEFORE_EXIT) 28 | }) 29 | 30 | await run(script) 31 | -------------------------------------------------------------------------------- /src/main/tips.ts: -------------------------------------------------------------------------------- 1 | // Name: Tips 2 | // Description: Docs for Script Kit Tips 3 | // Keyword: tips 4 | // Pass: true 5 | // Enter: Open Tips 6 | 7 | import { createTipsConfig } from "./main-helper.js" 8 | 9 | let tipsPath = kitPath("TIPS.md") 10 | 11 | await docs( 12 | tipsPath, 13 | createTipsConfig({ 14 | name: "Tips", 15 | guidePath: tipsPath, 16 | itemHeight: PROMPT.ITEM.HEIGHT.SM, 17 | input: arg?.input || "", 18 | placeholder: "Browse Tips", 19 | enter: `Create Script`, 20 | preventCollapse: true, 21 | onNoChoices: async input => { 22 | setPanel( 23 | md(`# Expected ${input} in the Tips? 24 | Tips are constantly evolving. If you're missing something, [suggest an edit](https://github.com/johnlindquist/kit-docs/blob/main/TIPS.md) to the tips. 25 | `) 26 | ) 27 | }, 28 | }) 29 | ) 30 | 31 | export {} 32 | -------------------------------------------------------------------------------- /src/workers/create-bin-worker.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path" 2 | import { Bin } from "../core/enum.js" 3 | import { createBinFromScript } from "../cli/lib/utils.js" 4 | import { parentPort } from "node:worker_threads" 5 | import type { Script } from "../types/core.ts" 6 | 7 | parentPort?.on( 8 | "message", 9 | async ({ command, filePath, execPath }) => { 10 | try { 11 | await createBinFromScript(Bin.scripts, { 12 | filePath, 13 | command, 14 | execPath, 15 | } as Script & { execPath: string }) 16 | // console.log(`Worker: Created bin from script: ${filePath} -> ${command}`) 17 | } catch (error) { 18 | console.log( 19 | `Worker: Error creating bin from script: ${filePath} -> ${command}`, 20 | error 21 | ) 22 | } 23 | parentPort?.postMessage({ filePath, command }) 24 | } 25 | ) 26 | -------------------------------------------------------------------------------- /src/ci/create-tag.ts: -------------------------------------------------------------------------------- 1 | let { Octokit } = await npm("@octokit/rest") 2 | 3 | let octokit = new Octokit({ 4 | request: { 5 | hook: require("@octokit/plugin-request-log").requestLog, 6 | }, 7 | auth: await env("GITHUB_TOKEN"), 8 | }) 9 | 10 | let owner = "johnlindquist" 11 | let repo = "kit" 12 | let branch = "main" 13 | let version = await arg("Version") 14 | 15 | let commitRef = await octokit.git.getRef({ 16 | owner, 17 | repo, 18 | ref: `heads/${branch}`, 19 | }) 20 | 21 | let object = commitRef.data.object.sha 22 | let tagCreateResponse = await octokit.git.createTag({ 23 | owner, 24 | repo, 25 | tag: version, 26 | message: version, 27 | object, 28 | type: "commit", 29 | }) 30 | 31 | let createRefResponse = await octokit.git.createRef({ 32 | owner, 33 | repo, 34 | ref: `refs/tags/${version}`, 35 | sha: tagCreateResponse.data.sha, 36 | }) 37 | 38 | export {} 39 | -------------------------------------------------------------------------------- /src/cli/share-script.ts: -------------------------------------------------------------------------------- 1 | //Menu: Share Script as Gist 2 | //Description: Create a gist from the selected script 3 | 4 | import { authenticate } from "../api/kit.js" 5 | 6 | let { filePath, command } = await selectScript( 7 | `Share which script?` 8 | ) 9 | 10 | let octokit = await authenticate() 11 | 12 | div(md(`## Creating Gist...`)) 13 | setLoading(true) 14 | 15 | let fileBasename = path.basename(filePath) 16 | 17 | let response = await octokit.rest.gists.create({ 18 | files: { 19 | [fileBasename]: { 20 | content: await readFile(filePath, "utf8"), 21 | }, 22 | }, 23 | public: true, 24 | }) 25 | 26 | let link = 27 | response.data?.files[command + path.extname(filePath)] 28 | .raw_url 29 | 30 | copy(link) 31 | 32 | setLoading(false) 33 | 34 | await div( 35 | await highlight(`## Copied Gist to Clipboard 36 | 37 | [${link}](${link}) 38 | `) 39 | ) 40 | 41 | export {} 42 | -------------------------------------------------------------------------------- /src/setup/ensure-scriptlets.test.ts: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | import { readFile, rm } from "node:fs/promises" 3 | import { ensureDir } from "fs-extra" 4 | import { tmpPath } from "../api/kit" 5 | import { kenvPath, isFile } from "../core/utils" 6 | import { rimraf } from "rimraf" 7 | 8 | test("ensure-scriptlets creates a scriptlets.md in kenv if it doesn't exist", async (t) => { 9 | const kenvMockPath = tmpPath(".kenv-mock") 10 | process.env.KENV = kenvMockPath 11 | 12 | await rimraf(kenvMockPath) 13 | 14 | let mainMdPath = kenvPath("scriptlets", "scriptlets.md") 15 | const before = await isFile(mainMdPath) 16 | 17 | await ensureDir(kenvMockPath) 18 | global.ensureDir = ensureDir 19 | global.kenvPath = kenvPath 20 | 21 | await import("./ensure-scriptlets") 22 | 23 | await isFile(mainMdPath) 24 | const mainMd = await readFile(mainMdPath, "utf8") 25 | t.is(before, false) 26 | t.is(mainMd, "") 27 | }) 28 | -------------------------------------------------------------------------------- /src/cli/paste-as-markdown.ts: -------------------------------------------------------------------------------- 1 | //Menu: Paste as Markdown 2 | //Description: Pastes Script as Markdown 3 | 4 | import { authenticate } from "../api/kit.js" 5 | 6 | let { command, filePath } = await selectScript( 7 | `Share which script?` 8 | ) 9 | 10 | let octokit = await authenticate() 11 | 12 | let fileBasename = path.basename(filePath) 13 | 14 | let content = await readFile(filePath, "utf8") 15 | 16 | let response = await octokit.rest.gists.create({ 17 | files: { 18 | [fileBasename]: { 19 | content: await readFile(filePath, "utf8"), 20 | }, 21 | }, 22 | public: true, 23 | }) 24 | 25 | let gistUrl = response.data.files[fileBasename].raw_url 26 | 27 | let link = `https://scriptkit.com/api/new?name=${command}&url=${gistUrl}"` 28 | 29 | await setSelectedText( 30 | "```js\n" + 31 | content.trim() + 32 | "\n```" + 33 | `\n\n[Open ${command} in Script Kit](${link})` 34 | ) 35 | 36 | export {} 37 | -------------------------------------------------------------------------------- /src/main/kit.ts: -------------------------------------------------------------------------------- 1 | // Name: Kit Settings 2 | // Description: Manage Kit Settings 3 | // Keyword: kit 4 | // Pass: true 5 | 6 | import { createGuideConfig } from "./main-helper.js" 7 | 8 | // TODO: Create a settings file for the kenv/kit paths 9 | 10 | let cliScript = await docs( 11 | kitPath("KIT.md"), 12 | createGuideConfig({ 13 | name: "Kit", 14 | guidePath: kitPath("KIT.md"), 15 | input: arg?.input || arg?.pass || "", 16 | placeholder: "Kit Actions", 17 | preventCollapse: true, 18 | onNoChoices: async input => { 19 | setPanel( 20 | md(`# Expected ${input} in the Kit Menu? 21 | Feel free to [suggest an edit](https://github.com/johnlindquist/kit/blob/main/KIT.md) to the Kit menu or open an issue on GitHub. 22 | `) 23 | ) 24 | }, 25 | }) 26 | ) 27 | 28 | if (cliScript.startsWith("http")) { 29 | open(cliScript) 30 | } else { 31 | await run(kitPath(cliScript)) 32 | } 33 | 34 | export {} 35 | -------------------------------------------------------------------------------- /src/cli/kenv-change-dir.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | export {} 4 | 5 | // TODO: Testing opening app from Alfred/Terminal 6 | let newKenvParentPath = await path({ 7 | hint: `Select the parent directory that will contain your .kenv`, 8 | }) 9 | 10 | let moveKenv = await arg("Move ~/.kenv to directory?", [ 11 | "no", 12 | "yes", 13 | ]) 14 | if (moveKenv === "yes") { 15 | await move( 16 | kenvPath(), 17 | path.join(newKenvParentPath, ".kenv") 18 | ) 19 | await replace({ 20 | files: [path.join(newKenvParentPath, ".kenv", ".env")], 21 | from: /KENV=.*/, 22 | to: `KENV=${path.join(newKenvParentPath, ".kenv")}`, 23 | }) 24 | } 25 | 26 | await sendWait( 27 | Channel.KENV_NEW_PATH, 28 | path.join(newKenvParentPath, ".kenv") 29 | ) 30 | 31 | await div({ 32 | html: md(`# Quit Now? 33 | 34 | Kit.app must quit before for the new .kenv to be recognized. 35 | `), 36 | enter: "Quit", 37 | }) 38 | 39 | await sendWait(Channel.QUIT_AND_RELAUNCH) 40 | -------------------------------------------------------------------------------- /src/main/api.ts: -------------------------------------------------------------------------------- 1 | // Name: API 2 | // Description: Browse Kit API 3 | // Keyword: api 4 | // Pass: true 5 | // Enter: Open API 6 | 7 | import { createGuideConfig } from "./main-helper.js" 8 | 9 | let selectedDoc = await docs( 10 | kitPath("API.md"), 11 | createGuideConfig({ 12 | name: "API", 13 | guidePath: kitPath("API.md"), 14 | itemHeight: PROMPT.ITEM.HEIGHT.SM, 15 | input: arg?.input || arg?.pass || "", 16 | placeholder: "Browse API", 17 | enter: `Open API.md`, 18 | preventCollapse: true, 19 | onNoChoices: async input => { 20 | setPanel( 21 | md(`# Expected ${input} in the Docs? 22 | This api docs are constantly evolving. If you're missing something, [suggest an edit](https://github.com/johnlindquist/kit-docs/blob/main/API.md) to the api docs or open an issue on GitHub. 23 | `) 24 | ) 25 | }, 26 | }) 27 | ) 28 | 29 | let url = 30 | "https://github.com/johnlindquist/kit-docs/blob/main/API.md" 31 | 32 | open(url) 33 | 34 | export {} 35 | -------------------------------------------------------------------------------- /src/cli/share-script-as-link.ts: -------------------------------------------------------------------------------- 1 | //Menu: Share Script as scriptkit.com link 2 | //Description: Create a gist and share from ScriptKit 3 | import { authenticate } from "../api/kit.js" 4 | 5 | let { filePath, command } = await selectScript( 6 | `Share which script?` 7 | ) 8 | 9 | div(md(`## Creating Gist...`)) 10 | setLoading(true) 11 | 12 | let octokit = await authenticate() 13 | 14 | let fileBasename = path.basename(filePath) 15 | setDescription(`Creating link...`) 16 | 17 | let response = await octokit.rest.gists.create({ 18 | files: { 19 | [fileBasename]: { 20 | content: await readFile(filePath, "utf8"), 21 | }, 22 | }, 23 | public: true, 24 | }) 25 | 26 | let link = `https://scriptkit.com/api/new?name=${command}&url=${response.data.files[fileBasename].raw_url}` 27 | copy(link) 28 | 29 | let message = `Copied share link to clipboard` 30 | 31 | setAlwaysOnTop(true) 32 | 33 | await div( 34 | await highlight(`## ${message} 35 | 36 | [${link}](${link}) 37 | `) 38 | ) 39 | 40 | export {} 41 | -------------------------------------------------------------------------------- /src/cli/change-main-shortcut.ts: -------------------------------------------------------------------------------- 1 | // Name: Change Shortcut 2 | 3 | let shortcut = "" 4 | let confirm = false 5 | 6 | import { getMainScriptPath } from "../core/utils.js" 7 | 8 | while (!confirm) { 9 | ;({ shortcut } = await hotkey({ 10 | description: `Change shortcut for main menu`, 11 | placeholder: `Enter a key combo:`, 12 | })) 13 | confirm = await arg(`Accept`, [ 14 | { 15 | name: `Accept: "${shortcut}"`, 16 | info: true, 17 | }, 18 | { 19 | name: `[Y]es`, 20 | value: true, 21 | }, 22 | { 23 | name: `[R]etry`, 24 | value: false, 25 | }, 26 | ]) 27 | } 28 | await run( 29 | kitPath("cli", "set-env-var.js"), 30 | "KIT_MAIN_SHORTCUT", 31 | shortcut 32 | ) 33 | 34 | arg(`Shortcut Changed`, [ 35 | { 36 | name: `Shortcut changed to ${shortcut}`, 37 | info: true, 38 | }, 39 | ]) 40 | 41 | await wait(1500) 42 | submit("") 43 | 44 | if (process.env.KIT_CONTEXT === "app") { 45 | await run(getMainScriptPath()) 46 | } 47 | 48 | export {} 49 | -------------------------------------------------------------------------------- /src/cli/new-scriptlet.ts: -------------------------------------------------------------------------------- 1 | /* 2 | # New Scriptlet 3 | 4 | Create a new scriptlet 5 | 6 | Opens ~/.kenv/scriptlets/scriptlets.md in your selected editor 7 | */ 8 | 9 | // Name: New Scriptlet 10 | // Description: Create a new scriptlet 11 | // Pass: true 12 | // Log: false 13 | // Keyword: np 14 | import { keywordInputTransformer } from "../core/utils.js" 15 | 16 | await ensureDir(kenvPath("scriptlets")) 17 | let scriptletsPath = kenvPath("scriptlets", "scriptlets.md") 18 | await ensureFile(scriptletsPath) 19 | 20 | let name = arg?.pass || await arg({ 21 | placeholder: "Scriptlet Name", 22 | enter: "Create Scriptlet", 23 | }) 24 | 25 | if(arg?.keyword){ 26 | name = keywordInputTransformer(arg.keyword)(name) 27 | } 28 | 29 | if (name) { 30 | let content = await readFile(scriptletsPath, "utf-8") 31 | let whitespace = "" 32 | if (content.trim() !== "") { 33 | whitespace = "\n\n" 34 | } 35 | 36 | await appendFile( 37 | scriptletsPath, 38 | `${whitespace}## ${name} 39 | 40 | \`\`\`ts 41 | 42 | \`\`\` 43 | ` 44 | ) 45 | } 46 | await edit(scriptletsPath) 47 | exit() 48 | export {} 49 | -------------------------------------------------------------------------------- /src/cli/kenv.test.js: -------------------------------------------------------------------------------- 1 | import ava from "ava" 2 | import "../../test-sdk/config.js" 3 | import { pathToFileURL } from "node:url" 4 | 5 | let kenvName = "mock-kenv" 6 | ava.serial("kenv create", async (t) => { 7 | let { stdout } = await exec(`kit kenv-create ${kenvName}`, { 8 | env: { 9 | ...process.env, 10 | KIT_NODE_PATH: process.execPath, 11 | KIT_MODE: "js" 12 | } 13 | }) 14 | 15 | let kenvsPath = kenvPath("kenvs") 16 | let kenvsPathFileUrl = path.join(kenvsPath, kenvName) 17 | 18 | t.log({ stdout, kenvsPath }) 19 | 20 | let kenvs = await readdir(kenvsPath) 21 | let kenvExists = await pathExists(kenvsPathFileUrl) 22 | t.log({ kenvs, kenvExists }) 23 | 24 | t.true(kenvExists) 25 | }) 26 | 27 | ava.serial("kenv remove", async (t) => { 28 | await exec(`kit kenv-rm ${kenvName}`, { 29 | env: { 30 | ...process.env, 31 | KIT_NODE_PATH: process.execPath, 32 | KIT_MODE: "js" 33 | } 34 | }) 35 | 36 | let kenvs = await readdir(kenvPath("kenvs")) 37 | let kenvExists = await pathExists(kenvPath("kenvs", kenvName)) 38 | t.log({ kenvs, kenvExists }) 39 | 40 | t.false(kenvExists) 41 | }) 42 | -------------------------------------------------------------------------------- /src/cli/manage-npm.ts: -------------------------------------------------------------------------------- 1 | // Name: npm 2 | // Description: Add/remove npm packages 3 | // Keyword: npm 4 | 5 | import { CLI } from "../cli" 6 | import { cliShortcuts } from "../core/utils.js" 7 | 8 | while (true) { 9 | let script = await arg( 10 | { 11 | placeholder: "What would you like to do?", 12 | shortcuts: cliShortcuts, 13 | enter: "Select" 14 | }, 15 | [ 16 | { 17 | name: "Install an npm package", 18 | value: "install", 19 | shortcode: "i" 20 | }, 21 | { 22 | name: "Update an npm package", 23 | value: "update-package", 24 | shortcode: "up" 25 | }, 26 | { 27 | name: "Uninstall an npm package", 28 | value: "uninstall", 29 | shortcode: "un" 30 | }, 31 | { 32 | name: "Get more info about an npm packaage", 33 | value: "more-info", 34 | shortcode: "info" 35 | }, 36 | { 37 | name: "Open a Terminal to Manually Install/Uninstall", 38 | value: "manual-npm" 39 | } 40 | ] 41 | ) 42 | 43 | setInput("") 44 | setFilterInput("") 45 | delete arg.keyword 46 | 47 | await cli(script as keyof CLI) 48 | } 49 | 50 | export {} 51 | -------------------------------------------------------------------------------- /src/pro/sponsor.ts: -------------------------------------------------------------------------------- 1 | // Name: Sponsors Only 2 | import { escapeShortcut, proPane } from "../core/utils.js" 3 | let sponsorUrl = 4 | "https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205" 5 | try { 6 | sponsorUrl = ( 7 | await readFile( 8 | kitPath("data", "sponsor-url.txt"), 9 | "utf-8" 10 | ) 11 | ).trim() 12 | } catch (error) { 13 | warn("Failed to read sponsor-url.txt") 14 | } 15 | let featureName = await arg("Feature Name") 16 | let content = `# Please sponsor to unlock ${featureName}` 17 | 18 | export let signInShortcut = { 19 | name: "Sign In", 20 | key: `${cmd}+enter`, 21 | bar: "right" as const, 22 | onPress: async () => { 23 | await run(kitPath("main", "sign-in.js")) 24 | }, 25 | } 26 | 27 | await div({ 28 | html: md(content + proPane()), 29 | description: `${featureName} requires a GitHub Sponsorship`, 30 | enter: "Open Sponsorship Page", 31 | shortcuts: [signInShortcut, escapeShortcut], 32 | width: 640, 33 | height: 640, 34 | alwaysOnTop: true, 35 | }) 36 | open(sponsorUrl) 37 | //# sourceMappingURL=sponsor.js.map 38 | -------------------------------------------------------------------------------- /src/main/guide.ts: -------------------------------------------------------------------------------- 1 | // Name: Guide 2 | // Description: Docs Script Kit Guide 3 | // Keyword: guide 4 | // Pass: true 5 | // Enter: Open Guide 6 | 7 | import { createGuideConfig } from "./main-helper.js" 8 | 9 | let selectedDoc = await docs( 10 | kitPath("GUIDE.md"), 11 | createGuideConfig({ 12 | name: "Guide", 13 | guidePath: kitPath("GUIDE.md"), 14 | itemHeight: PROMPT.ITEM.HEIGHT.SM, 15 | input: arg?.input || arg?.input || "", 16 | placeholder: "Browse Guide", 17 | enter: `Open GUIDE.md`, 18 | preventCollapse: true, 19 | onNoChoices: async input => { 20 | setPanel( 21 | md(`# Expected ${input} in the Guide? 22 | This guide is constantly evolving. If you're missing something, [suggest an edit](https://github.com/johnlindquist/kit-docs/blob/main/GUIDE.md) to the guide or open an issue on GitHub. 23 | `) 24 | ) 25 | }, 26 | }) 27 | ) 28 | 29 | // if selected docs is a url, then open it 30 | if (selectedDoc.startsWith("http")) { 31 | open(selectedDoc) 32 | } else { 33 | await run(kitPath("cli", selectedDoc)) 34 | await mainScript("", "Guide") 35 | } 36 | 37 | export {} 38 | -------------------------------------------------------------------------------- /src/main/suggest.ts: -------------------------------------------------------------------------------- 1 | // Name: Google Suggest 2 | // Description: Start typing to see Google Suggest results 3 | // Pass: true 4 | 5 | import "@johnlindquist/kit" 6 | import { escapeShortcut } from "../core/utils.js" 7 | 8 | let { default: suggest } = await import("suggestion") 9 | 10 | let input = await arg( 11 | { 12 | name: "Google Suggest", 13 | input: (arg?.pass as string) || "", 14 | placeholder: "Google suggest:", 15 | enter: `Paste`, 16 | // shortcuts: [escapeShortcut], 17 | resize: true, 18 | }, 19 | async input => { 20 | if (input?.length < 3) 21 | return [ 22 | { 23 | name: `Type at least 4 characters`, 24 | skip: true, 25 | info: true, 26 | }, 27 | ] 28 | 29 | return await new Promise((res, rej) => { 30 | suggest(input, (err, suggestions) => { 31 | if (err) { 32 | res(`## Error: ${err}`) 33 | } else { 34 | res(suggestions) 35 | } 36 | }) 37 | }) 38 | } 39 | ) 40 | 41 | // await run(kitPath("main", "google.js"), `--input`, input) 42 | setSelectedText(input) 43 | export {} 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 John Lindquist 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/cli/editor-history.ts: -------------------------------------------------------------------------------- 1 | // Name: Get Editor History 2 | 3 | import "@johnlindquist/kit" 4 | 5 | let history = await getEditorHistory() 6 | 7 | await arg( 8 | { 9 | placeholder: "Editor history", 10 | onNoChoices: async input => { 11 | setPanel( 12 | md(`## No History Found 13 | 14 | Open a file in the editor to add to the history.`) 15 | ) 16 | }, 17 | }, 18 | history.map(({ content, timestamp }) => { 19 | return { 20 | name: content.slice(0, 15), 21 | description: timestamp 22 | ?.replace("T", "-") 23 | .replace(/:/g, "-") 24 | .split(".")[0], 25 | preview: async () => { 26 | let { default: highlight } = 27 | global.__kitHighlight || 28 | (await import("highlight.js")) 29 | if (!global.__kitHighlight) 30 | global.__kitHighlight = { default: highlight } 31 | 32 | let result = highlight.highlightAuto(content).value 33 | 34 | return ` 39 | ${result}` 40 | }, 41 | } 42 | }) 43 | ) 44 | -------------------------------------------------------------------------------- /src/cli/new-quick.ts: -------------------------------------------------------------------------------- 1 | // Name: New Script 2 | // Description: Creates a new script 3 | import { default as generate } from "project-name-generator" 4 | 5 | let name = "quick-" + generate({ words: 2 }).dashed 6 | 7 | let scriptPath = path.join( 8 | kenvPath(), 9 | "scripts", 10 | name + ".js" 11 | ) 12 | 13 | let contents = [arg?.npm] 14 | .flatMap(x => x) 15 | .filter(Boolean) 16 | .map(npm => `let {} = await npm("${npm}")`) 17 | .join("\n") 18 | 19 | let template = arg?.template || (await env("KIT_TEMPLATE")) 20 | 21 | let templateContent = await readFile( 22 | kenvPath("templates", template + ".js"), 23 | "utf8" 24 | ) 25 | 26 | let templateCompiler = compile(templateContent) 27 | contents += templateCompiler({ name, ...env }) 28 | 29 | if (arg?.url) { 30 | contents = (await get(arg?.url)).data 31 | } 32 | 33 | await ensureDir(path.dirname(scriptPath)) 34 | await writeFile(scriptPath, contents) 35 | 36 | await cli("create-bin", "scripts", name) 37 | 38 | global.log( 39 | chalk`\nCreated a {green ${name}} script using the {yellow ${template}} template` 40 | ) 41 | 42 | await edit(scriptPath, kenvPath()) 43 | 44 | export {} 45 | -------------------------------------------------------------------------------- /src/cli/clear-script-database.ts: -------------------------------------------------------------------------------- 1 | // Description: Clear selected script database 2 | 3 | let { filePath } = await selectScript( 4 | `Open database for which script?` 5 | ) 6 | 7 | let { name, dir } = path.parse(filePath) 8 | 9 | let dbPath = path.resolve( 10 | path.dirname(dir), 11 | "db", 12 | `_${name}.json` 13 | ) 14 | 15 | let dbPathExists = await isFile(dbPath) 16 | 17 | if (dbPathExists) { 18 | let contents = await readFile(dbPath, "utf-8") 19 | let highlightedContents = await highlight( 20 | ` 21 | ## ${name} 22 | ~~~json 23 | ${contents} 24 | ~~~` 25 | ) 26 | let confirm = await arg( 27 | { 28 | placeholder: `Delete script data?`, 29 | hint: `[y]es/[n]o`, 30 | }, 31 | highlightedContents 32 | ) 33 | 34 | if (confirm !== "n") { 35 | await trash(dbPath) 36 | } 37 | let divP = div( 38 | md(`# Moved this file to trash: 39 | \`${dbPath}\` 40 | `) 41 | ) 42 | await wait(1500) 43 | submit("") 44 | await divP 45 | } else { 46 | await div( 47 | md(` 48 | # No db found for ${name} 49 | 50 | \`${dbPath}\` not found 51 | `) 52 | ) 53 | } 54 | 55 | await mainScript() 56 | 57 | export {} 58 | -------------------------------------------------------------------------------- /src/cli/update-kit-package.ts: -------------------------------------------------------------------------------- 1 | import { KIT_FIRST_PATH } from "../core/utils.js" 2 | import { createPackageManagerCommand } from "./lib/install.js" 3 | 4 | let file = JSON.parse( 5 | await readFile(kenvPath("package.json"), { 6 | encoding: "utf8", 7 | }) 8 | ) 9 | 10 | let packages = (await arg( 11 | { 12 | placeholder: `Which package do you want to update to latest?`, 13 | enter: "Update", 14 | }, 15 | [ 16 | ...Object.keys(file?.dependencies || []), 17 | ...Object.keys(file?.devDependencies || []), 18 | ].filter(k => !k.startsWith("@johnlindquist/kit")) 19 | )) as string[] 20 | 21 | //grab all the args you used `kit un jquery react` 22 | if (typeof packages == "string") { 23 | packages = [packages, ...args] 24 | } 25 | 26 | packages = packages.map(p => `${p}@latest`) 27 | 28 | let command = await createPackageManagerCommand( 29 | "i", 30 | packages 31 | ) 32 | 33 | let cwd = kenvPath() 34 | 35 | if (process.env.SCRIPTS_DIR) { 36 | cwd = kenvPath(process.env.SCRIPTS_DIR) 37 | } 38 | 39 | await term({ 40 | command, 41 | env: { 42 | ...global.env, 43 | PATH: KIT_FIRST_PATH, 44 | }, 45 | cwd, 46 | }) 47 | 48 | export {} 49 | -------------------------------------------------------------------------------- /src/cli/update-package.ts: -------------------------------------------------------------------------------- 1 | import { KIT_FIRST_PATH } from "../core/utils.js" 2 | import { createPackageManagerCommand } from "./lib/install.js" 3 | 4 | let file = JSON.parse( 5 | await readFile(kenvPath("package.json"), { 6 | encoding: "utf8", 7 | }) 8 | ) 9 | 10 | let packages = (await arg( 11 | { 12 | placeholder: `Which package do you want to update to latest?`, 13 | enter: "Update", 14 | }, 15 | [ 16 | ...Object.keys(file?.dependencies || []), 17 | ...Object.keys(file?.devDependencies || []), 18 | ].filter(k => !k.startsWith("@johnlindquist/kit")) 19 | )) as string[] 20 | 21 | //grab all the args you used `kit un jquery react` 22 | if (typeof packages == "string") { 23 | packages = [packages, ...args] 24 | } 25 | 26 | packages = packages.map(p => `${p}@latest`) 27 | 28 | let command = await createPackageManagerCommand( 29 | "i", 30 | packages 31 | ) 32 | 33 | let cwd = kenvPath() 34 | 35 | if (process.env.SCRIPTS_DIR) { 36 | cwd = kenvPath(process.env.SCRIPTS_DIR) 37 | } 38 | 39 | await term({ 40 | command, 41 | env: { 42 | ...global.env, 43 | PATH: KIT_FIRST_PATH, 44 | }, 45 | cwd, 46 | }) 47 | 48 | export {} 49 | -------------------------------------------------------------------------------- /src/core/defaults.ts: -------------------------------------------------------------------------------- 1 | import { ProcessType } from "./enum.js" 2 | import type { Choice, Script } from "../types" 3 | 4 | export const DEFAULT_LIST_WIDTH = 300 // 256; 5 | export const DEFAULT_WIDTH = 300 // 256; 6 | export const DEFAULT_EXPANDED_WIDTH = 768 7 | export const DEFAULT_HEIGHT = 8 | process.env.KIT_TIKTOK === "development" ? 1040 : 480 // Math.round((DEFAULT_EXPANDED_WIDTH * 10) / 16); // 480; 9 | export const INPUT_HEIGHT = 32 10 | export const TOP_HEIGHT = 80 11 | export const MIN_HEIGHT = TOP_HEIGHT 12 | export const MIN_TEXTAREA_HEIGHT = MIN_HEIGHT * 3 13 | export const MIN_WIDTH = 256 14 | export const DROP_HEIGHT = 232 15 | export const BUTTON_HEIGHT = 56 16 | export const EMOJI_WIDTH = 278 17 | export const EMOJI_HEIGHT = 380 18 | export const ZOOM_LEVEL = 19 | process.env.KIT_TIKTOK === "development" ? 2 : 0 20 | 21 | export const SPLASH_PATH = `__app__/splash-screen` 22 | 23 | export const noScript: Script = { 24 | id: "", 25 | filePath: "__app__/no-script", 26 | command: "", 27 | name: "", 28 | type: ProcessType.App, 29 | kenv: "", 30 | } 31 | 32 | export const noChoice: Choice = { 33 | id: "", 34 | name: "__app__/no-choice", 35 | } 36 | -------------------------------------------------------------------------------- /src/cli/log-widget.ts: -------------------------------------------------------------------------------- 1 | import "@johnlindquist/kit" 2 | import { getLogFromScriptPath } from "../core/utils.js" 3 | 4 | let scriptPath = await arg("Script Path") 5 | 6 | let Convert = await npm("ansi-to-html") 7 | let convert = new Convert() 8 | 9 | let logWidget = await widget( 10 | ` 11 | 21 |
22 |
23 |
24 | `, 25 | { 26 | width: 480, 27 | height: 320, 28 | alwaysOnTop: true, 29 | state: { 30 | log: `

Logging...

`, 31 | }, 32 | } 33 | ) 34 | 35 | let currentHtml = `` 36 | let handleLog = line => { 37 | let lineHtml = convert.toHtml(line) 38 | currentHtml += `

${lineHtml}

` 39 | 40 | logWidget.setState({ 41 | log: currentHtml, 42 | }) 43 | } 44 | 45 | let logPath = getLogFromScriptPath(scriptPath) 46 | await execLog(`tail -f ${logPath}`, handleLog) 47 | -------------------------------------------------------------------------------- /src/cli/toggle-background.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from "../core/enum.js" 2 | 3 | let { tasks } = await global.getBackgroundTasks() 4 | 5 | let scriptPath = await arg() 6 | 7 | let command = path.basename(scriptPath) 8 | 9 | let task = tasks.find(task => task.filePath === scriptPath) 10 | 11 | let toggleOrLog: "toggle" | "log" | "edit" = 12 | await global.arg( 13 | `${command} is ${task ? `running` : `stopped`}`, 14 | [ 15 | { 16 | name: `${task ? `Stop` : `Start`} ${command}`, 17 | value: `toggle`, 18 | id: uuid(), 19 | }, 20 | { 21 | name: `Edit ${command}`, 22 | value: `edit`, 23 | id: uuid(), 24 | }, 25 | { 26 | name: `View ${command}.log`, 27 | value: `log`, 28 | id: uuid(), 29 | }, 30 | ] 31 | ) 32 | 33 | if (toggleOrLog === "toggle") { 34 | global.send(Channel.TOGGLE_BACKGROUND, scriptPath) 35 | } 36 | 37 | if (toggleOrLog === "edit") { 38 | await global.edit(scriptPath, kenvPath()) 39 | } 40 | 41 | if (toggleOrLog === "log") { 42 | await global.edit( 43 | kenvPath("logs", `${command}.log`), 44 | kenvPath() 45 | ) 46 | } 47 | 48 | export {} 49 | -------------------------------------------------------------------------------- /src/cli/uninstall.ts: -------------------------------------------------------------------------------- 1 | import { KIT_FIRST_PATH } from "../core/utils.js" 2 | import { createPackageManagerCommand } from "./lib/install.js" 3 | 4 | let file = JSON.parse( 5 | await readFile(kenvPath("package.json"), { 6 | encoding: "utf8", 7 | }) 8 | ) 9 | 10 | let packageNames = (await arg( 11 | { 12 | placeholder: chalk`Which packages do you want to {red uninstall}`, 13 | enter: "Uninstall", 14 | }, 15 | [ 16 | ...Object.keys(file?.dependencies || []), 17 | ...Object.keys(file?.devDependencies || []), 18 | ].filter(k => !k.startsWith("@johnlindquist")) 19 | )) as string[] 20 | 21 | //grab all the args you used `kit un jquery react` 22 | if (typeof packageNames == "string") { 23 | packageNames = [packageNames, ...args] 24 | } 25 | let cwd = kenvPath() 26 | 27 | if (process.env.SCRIPTS_DIR) { 28 | cwd = kenvPath(process.env.SCRIPTS_DIR) 29 | } 30 | 31 | let command = await createPackageManagerCommand( 32 | "un", 33 | packageNames 34 | ) 35 | 36 | await term({ 37 | command, 38 | env: { 39 | ...global.env, 40 | PATH: KIT_FIRST_PATH, 41 | DISABLE_AUTO_UPDATE: "true", // Disable auto-update for zsh 42 | }, 43 | cwd, 44 | }) 45 | 46 | export {} 47 | -------------------------------------------------------------------------------- /src/cli/lib/install.ts: -------------------------------------------------------------------------------- 1 | import { kitPnpmPath } from "../../core/resolvers.js" 2 | import { existsSync } from "node:fs" 3 | export async function createPackageManagerCommand( 4 | command: "i" | "un", 5 | packageNames: string[] 6 | ) { 7 | let isYarn = await isFile(kenvPath("yarn.lock")) 8 | 9 | let packageManager = isYarn ? "yarn" : "pnpm" 10 | let toolCommand = command as 11 | | "add" 12 | | "i" 13 | | "remove" 14 | | "uninstall" 15 | 16 | if (isYarn) { 17 | if (command === "i") { 18 | toolCommand = "add" 19 | } 20 | } 21 | 22 | if (!isYarn) { 23 | // Check if the package manager exists with or without .exe extension 24 | const pm = packageManager + (global.isWin ? ".exe" : "") 25 | if (existsSync(kitPnpmPath(pm))) { 26 | // Use the full path to the package manager, but without the .exe extension 27 | packageManager = kitPnpmPath(packageManager) 28 | } 29 | } 30 | 31 | // Combine package manager and command 32 | let installCommand = `${packageManager} ${command}` 33 | 34 | let packages = packageNames.join(" ") 35 | let fullCommand = 36 | `${installCommand} -D ${packages}`.trim() 37 | 38 | return fullCommand 39 | } 40 | -------------------------------------------------------------------------------- /src/main/sticky.ts: -------------------------------------------------------------------------------- 1 | /* 2 | # Sticky Notes 3 | 4 | Opens a quick editor for taking notes. 5 | 6 | Notes are saved to `~/.kenv/sticky.md`. 7 | */ 8 | 9 | // Name: Sticky Pad 10 | // Description: Take Quick Notes 11 | // Cache: true 12 | // Trigger: , 13 | 14 | let stickyPath = kenvPath("sticky.md") 15 | let contents = await ensureReadFile( 16 | stickyPath, 17 | ` 18 | # Sticky Notes 19 | `.trim() 20 | ) 21 | 22 | let changed = false 23 | 24 | if (arg?.pass) { 25 | contents = `${contents} 26 | ${arg?.pass}` 27 | } 28 | 29 | contents = await editor({ 30 | value: contents, 31 | scrollTo: "bottom", 32 | // footer: `Escape to save to ${stickyPath}`, 33 | shortcuts: [ 34 | { 35 | name: "Save and Close", 36 | key: "escape", 37 | onPress: async (input, { inputChanged }) => { 38 | changed = inputChanged 39 | await hide() 40 | await submit(input) 41 | }, 42 | bar: "right" 43 | } 44 | ], 45 | onAbandon: async (input, { inputChanged }) => { 46 | changed = inputChanged 47 | submit(input) 48 | }, 49 | onInput: async () => { 50 | changed = true 51 | } 52 | }) 53 | 54 | if (changed || arg?.pass) { 55 | await writeFile(stickyPath, contents + "\n") 56 | } 57 | 58 | export {} 59 | -------------------------------------------------------------------------------- /src/core/shebang.ts: -------------------------------------------------------------------------------- 1 | import type { Script, Scriptlet } from "../types" 2 | import untildify from "untildify" 3 | import { kenvPath } from "./utils.js" 4 | import { slash } from "./resolvers.js" 5 | 6 | export type ShebangConfig = { 7 | command: string 8 | args: string[] 9 | shell: boolean | string 10 | cwd: string 11 | filePath: string 12 | } 13 | export function parseShebang(script: Script | Scriptlet): ShebangConfig { 14 | let command = "" 15 | let args: string[] = [] 16 | let shell: boolean | string = true 17 | let cwd = kenvPath() 18 | let filePath = script.filePath 19 | 20 | if ("scriptlet" in script) { 21 | ;[command, ...args] = script.scriptlet.split(" ") 22 | if (script?.shell === false || script?.shell === "false") { 23 | shell = false 24 | } 25 | if (typeof script?.shell === "string") { 26 | shell = script.shell 27 | } 28 | } else if ("shebang" in script) { 29 | command = script.shebang 30 | ;[command, ...args] = script.shebang.split(" ") 31 | args.push(filePath) 32 | } 33 | 34 | if ("cwd" in script) { 35 | cwd = path.resolve(slash(untildify(script.cwd))) 36 | } 37 | 38 | return { 39 | command, 40 | args, 41 | shell, 42 | cwd, 43 | filePath 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/run/github-workflow.ts: -------------------------------------------------------------------------------- 1 | process.env.KIT_TARGET = "github-workflow" 2 | 3 | import os from "node:os" 4 | import { randomUUID } from "node:crypto" 5 | import { pathToFileURL } from "node:url" 6 | 7 | process.env.KIT_CONTEXT = "workflow" 8 | 9 | import { 10 | configEnv, 11 | resolveToScriptPath, 12 | kitPath, 13 | } from "../core/utils.js" 14 | 15 | let kitImport = async (...pathParts: string[]) => 16 | await import( 17 | pathToFileURL(kitPath(...pathParts)).href + 18 | "?uuid=" + 19 | randomUUID() 20 | ) 21 | 22 | await kitImport("api", "global.js") 23 | await kitImport("api", "kit.js") 24 | await kitImport("api", "lib.js") 25 | await import("../platform/base.js") 26 | 27 | try { 28 | await attemptImport(kenvPath('globals.ts')) 29 | } catch (error) { 30 | log('No user-defined globals.ts') 31 | } 32 | 33 | 34 | let platform = process.env?.PLATFORM || os.platform() 35 | 36 | await kitImport("platform", `${platform}.js`) 37 | 38 | configEnv() 39 | 40 | await kitImport("target", "terminal.js") 41 | 42 | global.core = await npm("@actions/core") 43 | global.github = await npm("@actions/github") 44 | 45 | let scriptPath = resolveToScriptPath( 46 | await arg("Path to script") 47 | ) 48 | await run(scriptPath) 49 | -------------------------------------------------------------------------------- /src/cli/kenv-distrust.ts: -------------------------------------------------------------------------------- 1 | // Name: Testing Extreme Caution 2 | 3 | import { getTrustedKenvsKey } from "../core/utils.js" 4 | 5 | let trustedKenvsKey = getTrustedKenvsKey() 6 | 7 | let currentTrustedKenvs = process.env[trustedKenvsKey] 8 | ?.split(",") 9 | .filter(Boolean) 10 | 11 | let hasTrustedKenvs = currentTrustedKenvs?.length > 0 12 | 13 | let trustedKenvs: any[] = [ 14 | { 15 | info: true, 16 | name: hasTrustedKenvs 17 | ? "Select a kenv to distrust" 18 | : "No trusted kenvs", 19 | description: hasTrustedKenvs 20 | ? `Prevent scripts from this kenv from running automatically` 21 | : `Use "Trust Kenv" to manually trust a kenv`, 22 | }, 23 | ...currentTrustedKenvs, 24 | ] 25 | 26 | // Removes the kenv from the .env 27 | let kenv = await arg( 28 | { 29 | placeholder: "Distrust which kenv", 30 | }, 31 | trustedKenvs 32 | ) 33 | 34 | if (typeof process?.env?.[trustedKenvsKey] === "string") { 35 | let newValue = process.env[trustedKenvsKey] 36 | .split(",") 37 | .filter(Boolean) 38 | .filter(k => k !== kenv) 39 | .join(",") 40 | 41 | await global.cli("set-env-var", trustedKenvsKey, newValue) 42 | } 43 | 44 | if (process.env.KIT_CONTEXT === "app") { 45 | await mainScript() 46 | } 47 | -------------------------------------------------------------------------------- /src/cli/search-docs.ts: -------------------------------------------------------------------------------- 1 | // Name: Search Docs 2 | // Description: Type to Search Docs 3 | // Log: false 4 | 5 | let search = async input => { 6 | if (!input) return 7 | try { 8 | let { stdout } = 9 | await $`grep -ri --include='*.md' '^## .*${input}' ~/.kit/docs` 10 | 11 | let results = stdout.split("\n") 12 | let choices = Promise.all( 13 | results.filter(Boolean).map(async result => { 14 | let [filePath, ...found] = result.split(":") 15 | let preview = found.join(" ") 16 | // let { stdout: h1 } = 17 | // await $`grep '^# .*' ${filePath}` 18 | 19 | return { 20 | name: preview.split(`## `)[1], 21 | value: filePath, 22 | description: filePath, 23 | } 24 | }) 25 | ) 26 | 27 | return choices 28 | } catch { 29 | return [`${input} not found`] 30 | } 31 | } 32 | 33 | while (true) { 34 | let doc = await arg( 35 | { 36 | placeholder: `Search docs:`, 37 | }, 38 | search 39 | ) 40 | let value = md(await readFile(doc, "utf-8")) 41 | 42 | await arg( 43 | { 44 | placeholder: `Hit "enter" to start a new search:`, 45 | className: "kit-docs", 46 | }, 47 | value 48 | ) 49 | } 50 | 51 | export {} 52 | -------------------------------------------------------------------------------- /src/cli/kenv-visit.ts: -------------------------------------------------------------------------------- 1 | //Description: Visit a Kenv Repo 2 | import { getKenvs } from "../core/utils.js" 3 | let kenv = await arg( 4 | "Visit which kenv?", 5 | ( 6 | await getKenvs() 7 | ).map(value => ({ 8 | name: path.basename(value), 9 | value: path.basename(value), 10 | })) 11 | ) 12 | // find remote repo from .git dir 13 | let gitConfigPath = kenvPath( 14 | "kenvs", 15 | kenv, 16 | ".git", 17 | "config" 18 | ) 19 | try { 20 | let gitConfig = await readFile(gitConfigPath, "utf8") 21 | let remoteOriginRegex = 22 | /\[remote "origin"]\s+(?:fetch\s?=.*\s+)?url\s?=\s?(.+)(?:\s+fetch\s?=.*\s+)?/ 23 | let match = gitConfig.match(remoteOriginRegex) 24 | if (match) { 25 | let url = match[1] 26 | if (url.startsWith("git@")) { 27 | url = url 28 | .replace("git@", "https://") 29 | .replace(/(?<=\w|\d):(?=\w|\d)/, "/") 30 | .replace(/.git$/, "") 31 | } 32 | await open(url) 33 | } else { 34 | await div( 35 | md(`## Could not find remote origin in git config 36 | ~~~ 37 | ${gitConfig} 38 | ~~~ 39 | `) 40 | ) 41 | } 42 | } catch (error) { 43 | await div( 44 | md(`## Failed to read 45 | 46 | ${gitConfigPath}`) 47 | ) 48 | } 49 | //# sourceMappingURL=kenv-visit.js.map 50 | -------------------------------------------------------------------------------- /src/main/open-with.ts: -------------------------------------------------------------------------------- 1 | // Exclude: true 2 | // Description: Open with... 3 | 4 | import type { Open } from "../types/packages" 5 | import { createAppChoices } from "./common.js" 6 | 7 | let filePath = await path() 8 | setName(``) 9 | 10 | let appsDb = await db("apps", async () => { 11 | setChoices([]) 12 | clearTabs() 13 | setPlaceholder("One sec...") 14 | setPanel( 15 | md(`# First Run: Indexing Apps and Caching Icons... 16 | 17 | Please hold a few seconds while Script Kit creates icons for your apps and preferences for future use. 18 | `) 19 | ) 20 | let choices = await createAppChoices() 21 | setFooter(``) 22 | return { 23 | choices 24 | } 25 | }) 26 | let input = "" 27 | let app = await arg( 28 | { 29 | input: (flag?.input as string) || "", 30 | placeholder: "Open with...", 31 | onInput: (i) => { 32 | input = i 33 | } 34 | }, 35 | appsDb.choices 36 | ) 37 | 38 | log(`Opening ${input} with ${app} and ${flag?.cmd ? "cmd" : "open"}`) 39 | if (flag?.cmd) { 40 | await remove(kitPath("db", "apps.json")) 41 | await run(kitPath("main", "app-launcher.js"), "--input", input) 42 | } else { 43 | await Promise.all([ 44 | (open as unknown as Open)(filePath, { 45 | app: { 46 | name: app 47 | } 48 | }), 49 | await hide() 50 | ]) 51 | } 52 | -------------------------------------------------------------------------------- /src/main/system-commands.ts: -------------------------------------------------------------------------------- 1 | // Name: System Commands 2 | // Description: Run system commands 3 | // Keyword: - 4 | // Cache: true 5 | 6 | let command = await arg("Select System Command", [ 7 | { 8 | name: "Mute", 9 | description: "Mute the system volume", 10 | value: "mute", 11 | }, 12 | { 13 | name: "Unmute", 14 | description: "Unmute the system volume", 15 | value: "unmute", 16 | }, 17 | { 18 | name: "Adjust Volume", 19 | description: "Set the system volume", 20 | value: "adjustVolume", 21 | }, 22 | { 23 | name: "Lock", 24 | description: "Lock screen", 25 | value: "lock", 26 | }, 27 | { 28 | name: "Sleep Screens", 29 | description: "Sleep the screens", 30 | value: "sleepScreens", 31 | }, 32 | { 33 | name: "Sleep", 34 | description: "Put system to sleep", 35 | value: "sleep", 36 | }, 37 | { 38 | name: "Caffeinate", 39 | description: "Keep system awake", 40 | value: "caffeinate", 41 | }, 42 | { 43 | name: "Shutdown", 44 | description: "Shutdown system", 45 | value: "shutdown", 46 | }, 47 | { 48 | name: "Quit All Apps", 49 | description: "Quit all apps", 50 | value: "quitAllApps", 51 | }, 52 | ]) 53 | 54 | global[command]() 55 | export {} 56 | -------------------------------------------------------------------------------- /src/setup/clone-examples.ts: -------------------------------------------------------------------------------- 1 | import { KIT_FIRST_PATH } from '../core/utils.js' 2 | 3 | async function downloadAndSetupExamples() { 4 | let destination = kenvPath('tmp') 5 | await download('https://github.com/johnlindquist/kit-examples-ts/archive/main.zip', destination, { 6 | extract: true, 7 | rejectUnauthorized: false 8 | }) 9 | 10 | let source = path.join(destination, 'kit-examples-main') 11 | let newDestination = kenvPath('kenvs', 'examples') 12 | await rename(source, newDestination) 13 | } 14 | 15 | async function pnpmInstallExamples() { 16 | await exec('pnpm i', { 17 | cwd: kenvPath('kenvs', 'examples'), 18 | env: { 19 | ...global.env, 20 | PATH: KIT_FIRST_PATH 21 | } 22 | }).catch((error) => { 23 | console.error(error) 24 | }) 25 | } 26 | 27 | let examplesDir = kenvPath('kenvs', 'examples') 28 | 29 | if (await isDir(examplesDir)) { 30 | await exec('git pull --rebase --autostash --stat', { 31 | cwd: examplesDir 32 | }) 33 | } else { 34 | try { 35 | await exec('git clone --depth 1 https://github.com/johnlindquist/kit-examples-ts examples', { 36 | cwd: kenvPath('kenvs') 37 | }) 38 | } catch (error) { 39 | await downloadAndSetupExamples() 40 | } 41 | } 42 | 43 | await pnpmInstallExamples() 44 | -------------------------------------------------------------------------------- /src/cli/system-events.ts: -------------------------------------------------------------------------------- 1 | // Name: System Events Scripts 2 | // Description: Select a Script to Edit 3 | 4 | import { getScripts } from "../core/db.js" 5 | import { cliShortcuts } from "../core/utils.js" 6 | let scriptsCache = await getScripts() 7 | let filePath = await arg( 8 | { 9 | placeholder: "Select System Event Script to Edit", 10 | enter: "Edit", 11 | shortcuts: cliShortcuts, 12 | onNoChoices: async () => { 13 | setPanel( 14 | md(`# No System Event Scripts Found 15 | 16 | Create a script with // System: metadata to add it to this list. 17 | 18 | Available events: 19 | 20 | - suspend 21 | - resume 22 | - on-ac 23 | - on-battery 24 | - shutdown 25 | - lock-screen 26 | - unlock-screen 27 | - user-did-become-active 28 | - user-did-resign-active 29 | - Read about the available events [here](https://www.electronjs.org/docs/latest/api/power-monitor#events) 30 | `) 31 | ) 32 | }, 33 | }, 34 | scriptsCache 35 | .filter(script => script?.system) 36 | .map(script => { 37 | return { 38 | name: script?.menu || script.command, 39 | description: `Runs on ${script.system}`, 40 | value: script.filePath, 41 | } 42 | }) 43 | ) 44 | await run(kitPath("cli", "edit-script.js"), filePath) 45 | -------------------------------------------------------------------------------- /src/cli/kenv-pull.ts: -------------------------------------------------------------------------------- 1 | // Description: Git Pull Kenv Repo 2 | 3 | import { getKenvs } from "../core/utils.js" 4 | 5 | let kenvs = (await getKenvs()).map(value => ({ 6 | name: path.basename(value), 7 | value, 8 | })) 9 | 10 | kenvs.unshift({ 11 | name: "main", 12 | value: kenvPath(), 13 | }) 14 | 15 | let dir = await arg("Pull which kenv", kenvs) 16 | 17 | cd(dir) 18 | 19 | await term({ 20 | command: `git fetch`, 21 | preview: md(`# Pulling a Kenv 22 | 23 | > The terminal only ran "git fetch" to show you what changes are available. 24 | 25 | You still need to run "git merge" to apply the changes. 26 | 27 | `), 28 | cwd: dir, 29 | shortcuts: [ 30 | { 31 | name: "Stash", 32 | key: `${cmd}+s`, 33 | bar: "right", 34 | onPress: async () => { 35 | term.write(`git stash`) 36 | }, 37 | }, 38 | { 39 | name: "Merge", 40 | key: `${cmd}+m`, 41 | bar: "right", 42 | onPress: async () => { 43 | term.write(`git merge`) 44 | }, 45 | }, 46 | { 47 | name: "Exit", 48 | key: `${cmd}+w`, 49 | bar: "right", 50 | onPress: async () => { 51 | submit("") 52 | }, 53 | }, 54 | ], 55 | }) 56 | 57 | await getScripts(false) 58 | 59 | await mainScript() 60 | 61 | export {} 62 | -------------------------------------------------------------------------------- /src/cli/new-from-url.ts: -------------------------------------------------------------------------------- 1 | // Description: Creates a script from an entered url 2 | 3 | import { highlightJavaScript } from "../api/kit.js" 4 | import { 5 | checkIfCommandExists, 6 | stripMetadata, 7 | } from "../core/utils.js" 8 | import { prependImport } from "./lib/utils.js" 9 | 10 | let url = await arg({ 11 | placeholder: "Enter script url:", 12 | }) 13 | 14 | if (url.includes("gist.github.com")) { 15 | url = `${url}/raw ` 16 | } 17 | 18 | let contents = (await get(url)).data 19 | if (!arg?.keepMetadata) 20 | contents = stripMetadata(contents, [ 21 | "Menu", 22 | "Name", 23 | "Author", 24 | "Twitter", 25 | "Alias", 26 | "Description", 27 | ]) 28 | 29 | if (url.endsWith(".js")) { 30 | let nameFromUrl = url.split("/").pop().replace(".js", "") 31 | updateArgs([nameFromUrl]) 32 | } 33 | 34 | let name = await arg({ 35 | placeholder: "Enter a name for your script:", 36 | validate: checkIfCommandExists, 37 | panel: await highlightJavaScript(contents), 38 | }) 39 | 40 | let scriptPath = path.join( 41 | kenvPath("scripts"), 42 | name + ".js" 43 | ) 44 | 45 | contents = prependImport(contents) 46 | await writeFile(scriptPath, contents) 47 | 48 | await cli("create-bin", "scripts", name) 49 | 50 | await run(kitPath("cli", "edit-script.js"), scriptPath) 51 | 52 | export {} 53 | -------------------------------------------------------------------------------- /src/core/scriptlet.utils.ts: -------------------------------------------------------------------------------- 1 | import type { Flags } from "../types" 2 | 3 | export function processConditionals(str: string, flag?: Flags): string { 4 | const regex = 5 | /{{#if\s+([\w-]+)}}((?:(?!{{#if)(?!{{\/if}}).|\n)*?)((?:{{else\s+if\s+([\w-]+)}}((?:(?!{{#if)(?!{{\/if}}).|\n)*?))*)(?:{{else}}((?:(?!{{#if)(?!{{\/if}}).|\n)*?))?{{\/if}}/g 6 | 7 | let result = str 8 | let lastResult: string 9 | 10 | do { 11 | lastResult = result 12 | 13 | result = result.replace( 14 | regex, 15 | (match, condition, ifContent, elseIfBlock, ...args) => { 16 | if (flag?.[condition]) { 17 | return ifContent 18 | } 19 | 20 | // Process else-if conditions 21 | const elseIfRegex = 22 | /{{else\s+if\s+([\w-]+)\s*}}((?:(?!{{else\s+if)(?!{{else}})(?!{{\/if}}).|\n)*)/g 23 | let elseIfMatch: RegExpExecArray | null 24 | while (true) { 25 | elseIfMatch = elseIfRegex.exec(elseIfBlock) 26 | if (elseIfMatch === null) break 27 | const [, elseIfCondition, elseIfContent] = elseIfMatch 28 | if (flag?.[elseIfCondition]) { 29 | return elseIfContent 30 | } 31 | } 32 | 33 | // Handle else content 34 | const elseContent = args[args.length - 3] 35 | 36 | return elseContent || "" 37 | } 38 | ) 39 | } while (result !== lastResult) 40 | 41 | return result 42 | } 43 | -------------------------------------------------------------------------------- /src/cli/schedule.ts: -------------------------------------------------------------------------------- 1 | // Name: View Schedule 2 | // Description: Select a scheduled script to edit 3 | 4 | import { Schedule } from "../types/kitapp" 5 | import { 6 | escapeShortcut, 7 | cliShortcuts, 8 | closeShortcut, 9 | parseScript, 10 | } from "../core/utils.js" 11 | 12 | import { 13 | formatDistanceToNowStrict, 14 | format, 15 | compareAsc, 16 | } from "date-fns" 17 | 18 | let { schedule } = await global.getSchedule() 19 | 20 | let choices = ( 21 | await Promise.all( 22 | schedule.map(async ({ filePath, date }) => { 23 | let script = await parseScript(filePath) 24 | let d = new Date(date) 25 | return { 26 | date, 27 | name: script?.menu || script.command, 28 | description: `Next ${formatDistanceToNowStrict( 29 | d 30 | )} - ${format(d, "MMM eo, h:mm:ssa ")} - ${ 31 | script?.schedule 32 | }`, 33 | value: filePath, 34 | } as Schedule 35 | }) 36 | ) 37 | ).sort(({ date: a }, { date: b }) => 38 | compareAsc(new Date(a), new Date(b)) 39 | ) as Schedule[] 40 | 41 | let filePath = await arg( 42 | { 43 | placeholder: "Select a scheduled script to edit", 44 | enter: "Select", 45 | shortcuts: cliShortcuts, 46 | }, 47 | choices 48 | ) 49 | 50 | await run(kitPath("cli", "edit-script.js"), filePath) 51 | 52 | export {} 53 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { homedir } from "os" 2 | import path from "path" 3 | import { URL, fileURLToPath } from "url" 4 | 5 | let kitRun = async (command?: string, ..._args: string[]) => { 6 | process.env.KIT = 7 | process.env.KIT || path.dirname(fileURLToPath(new URL(import.meta.url))) 8 | 9 | await import("./api/global.js") 10 | await import("./api/kit.js") 11 | await import("./api/lib.js") 12 | await import("./target/terminal.js") 13 | 14 | if (command) { 15 | return await global.run(command, ..._args) 16 | } 17 | } 18 | 19 | if (!process?.env?.KIT_TARGET) { 20 | await kitRun() 21 | } 22 | 23 | export * from "./api/kit.js" 24 | export * from "./core/utils.js" 25 | 26 | let dirs = ["cli", "main"] 27 | 28 | let kitGet = (_target: any, key: string, _receiver: any) => { 29 | if ((global as any)[key] && !dirs.includes(key)) { 30 | return (global as any)[key] 31 | } 32 | 33 | try { 34 | return new Proxy( 35 | {}, 36 | { 37 | get: async (_target, module: string, _receiver) => { 38 | let modulePath = `../${key}/${module}.js?${global.uuid()}` 39 | return await import(modulePath) 40 | } 41 | } 42 | ) 43 | } catch (error) { 44 | console.warn(error) 45 | } 46 | } 47 | 48 | let kitDefault = new Proxy(kitRun, { 49 | get: kitGet 50 | }) 51 | 52 | global.kit = kitDefault 53 | 54 | export default kitDefault 55 | -------------------------------------------------------------------------------- /src/core/is.ts: -------------------------------------------------------------------------------- 1 | import { constants } from "node:fs" 2 | import { platform } from "node:os" 3 | 4 | export let isWin = platform().startsWith("win") 5 | export let isMac = platform().startsWith("darwin") 6 | export let isLinux = platform().startsWith("linux") 7 | 8 | export let isJsh = () => { 9 | return process.env?.SHELL?.includes("jsh") 10 | } 11 | 12 | export let isDir = async (dir: string): Promise => { 13 | try { 14 | try { 15 | let stats = await lstat(dir).catch(() => { 16 | return { 17 | isDirectory: () => false 18 | } 19 | }) 20 | 21 | return stats?.isDirectory() 22 | } catch (error) { 23 | log(error) 24 | } 25 | 26 | return false 27 | } catch { 28 | return false 29 | } 30 | } 31 | 32 | export let isFile = async (file: string): Promise => { 33 | try { 34 | await access(file, constants.F_OK) 35 | let stats = await lstat(file).catch(() => { 36 | return { 37 | isFile: () => false 38 | } 39 | }) 40 | return stats?.isFile() 41 | } catch { 42 | return false 43 | } 44 | } 45 | 46 | export let isBin = async (bin: string): Promise => { 47 | if (isJsh()) return false 48 | try { 49 | const result = spawnSync("command", ["-v", bin], { 50 | stdio: "ignore", 51 | windowsHide: true 52 | }) 53 | return result.status === 0 54 | } catch { 55 | return false 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/paste-snippet.ts: -------------------------------------------------------------------------------- 1 | // Name: Paste Snippet 2 | 3 | import "@johnlindquist/kit" 4 | import { getSnippet, templatePlaceholdersRegex } from "../core/utils.js" 5 | 6 | let snippet = "" 7 | 8 | let contents = await readFile(arg.filePath, "utf8") 9 | let { metadata, snippet: snippetFromFile } = getSnippet(contents) 10 | snippet = snippetFromFile.trim() 11 | 12 | updateArgs(args); 13 | if (args?.length > 0 && snippet.includes("$0")) { 14 | snippet = snippet.replaceAll("$0", args?.shift()); 15 | } 16 | 17 | // Find ${selection} and replace with selected text 18 | if (snippet.includes("$SELECTION")) { 19 | let selectedText = await getSelectedText() 20 | snippet = snippet.replaceAll("$SELECTION", selectedText) 21 | } 22 | 23 | if (snippet.includes("$CLIPBOARD")) { 24 | let clipboard = await paste() 25 | snippet = snippet.replaceAll("$CLIPBOARD", clipboard) 26 | } 27 | 28 | if (snippet.includes("$HOME")) { 29 | snippet = snippet.replaceAll("$HOME", home()) 30 | } 31 | 32 | if (snippet.match(templatePlaceholdersRegex)) { 33 | setInput("") // clearing keyword 34 | snippet = await template(snippet, { 35 | description: "Fill in the template", 36 | shortcuts: [ 37 | { 38 | key: `${cmd}+s`, 39 | name: "Paste Snippet", 40 | onPress: (input) => { 41 | submit(input) 42 | }, 43 | bar: "right" 44 | } 45 | ] 46 | }) 47 | } 48 | await setSelectedText(snippet) 49 | -------------------------------------------------------------------------------- /src/cli/share-script-as-markdown.ts: -------------------------------------------------------------------------------- 1 | //Menu: Share Script for Kit Discussion 2 | //Description: Create a gist and copy discussion content to clipboard 3 | 4 | import { authenticate } from "../api/kit.js" 5 | 6 | let { filePath, command } = await selectScript( 7 | `Share which script?` 8 | ) 9 | 10 | let octokit = await authenticate() 11 | 12 | let fileBasename = path.basename(filePath) 13 | const fileExtension = path.extname(filePath).replace('.', '') 14 | 15 | div(md(`## Creating Gist...`)) 16 | setLoading(true) 17 | 18 | let content = await readFile(filePath, "utf8") 19 | let response = await octokit.rest.gists.create({ 20 | files: { 21 | [fileBasename]: { 22 | content: await readFile(filePath, "utf8"), 23 | }, 24 | }, 25 | public: true, 26 | }) 27 | 28 | const mdLanguage = ['js', 'jsx', 'ts', 'tsx'].includes(fileExtension) ? fileExtension : 'js' 29 | 30 | let gistUrl = response.data.files[fileBasename].raw_url 31 | 32 | let link = `https://scriptkit.com/api/new?name=${command}&url=${gistUrl}"` 33 | 34 | let discussionPost = `[Open ${command} in Script Kit](${link}) 35 | 36 | \`\`\`${mdLanguage} 37 | ${content} 38 | \`\`\` 39 | ` 40 | 41 | copy(discussionPost) 42 | 43 | setAlwaysOnTop(true) 44 | let message = `Copied ${command} to clipboard as markdown` 45 | await div( 46 | await highlight(`## ${message} 47 | 48 | ${discussionPost}`) 49 | ) 50 | export {} 51 | -------------------------------------------------------------------------------- /src/cli/edit-doc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | escapeShortcut, 3 | closeShortcut, 4 | cmd, 5 | setMetadata, 6 | } from "../core/utils.js" 7 | let scriptPath = await arg() 8 | // TODO: centralize .ts/.js finding logic 9 | let { name, dir } = path.parse(scriptPath) 10 | 11 | if (scriptPath.endsWith(".mjs")) { 12 | scriptPath = path.resolve( 13 | dir, 14 | "..", 15 | "scripts", 16 | name + ".ts" 17 | ) 18 | } 19 | 20 | let docPath = path.resolve( 21 | path.dirname(path.dirname(scriptPath)), 22 | "docs", 23 | name + ".md" 24 | ) 25 | 26 | await ensureFile(docPath) 27 | 28 | if ( 29 | process.env.KIT_EDITOR === "kit" && 30 | process.env.KIT_CONTEXT === "app" 31 | ) { 32 | let value = await readFile(docPath, "utf-8") 33 | await editor({ 34 | value, 35 | description: docPath, 36 | language: "md", 37 | shortcuts: [ 38 | { 39 | ...escapeShortcut, 40 | onPress: async input => { 41 | await writeFile(docPath, input) 42 | await mainScript() 43 | }, 44 | }, 45 | 46 | closeShortcut, 47 | { 48 | name: `Save`, 49 | key: `${cmd}+s`, 50 | onPress: async input => { 51 | await writeFile(docPath, input) 52 | await mainScript() 53 | }, 54 | bar: "right", 55 | }, 56 | ], 57 | }) 58 | } else { 59 | await edit(docPath, kenvPath()) 60 | } 61 | -------------------------------------------------------------------------------- /src/api/packages/trash.ts: -------------------------------------------------------------------------------- 1 | import { globby } from "globby" 2 | import { Channel } from "../../core/enum.js" 3 | import fs from "node:fs" 4 | import { rimraf } from "rimraf" 5 | 6 | export interface Options { 7 | readonly glob?: boolean 8 | } 9 | 10 | export default async function trash( 11 | input: string | readonly string[], 12 | options: Options = { glob: true } 13 | ): Promise { 14 | // Normalize input to an array of strings 15 | const inputs = Array.isArray(input) ? input : [input] 16 | 17 | // Use globby to match files if glob option is enabled 18 | const pathsToTrash = options.glob 19 | ? await globby(inputs.map((input) => input.replace(/\\/g, "/"))) 20 | : inputs 21 | 22 | if (process.env.KIT_CONTEXT === "app") { 23 | return await sendWaitLong(Channel.TRASH, pathsToTrash) 24 | } 25 | 26 | // Iterate over each path 27 | for (const item of pathsToTrash) { 28 | // Make sure the path exists 29 | const stats = await lstat(item) 30 | if (!stats) { 31 | throw new Error(`Path does not exist: ${item}`) 32 | } 33 | 34 | // Check if the path is a directory or a file 35 | if (stats.isDirectory()) { 36 | // Delete directory and its content 37 | await rimraf(item) 38 | } else { 39 | // Delete file 40 | await fs.promises.unlink(item) 41 | } 42 | } 43 | } 44 | 45 | global.trash = trash 46 | global.rm = trash 47 | 48 | export {} 49 | -------------------------------------------------------------------------------- /src/cli/add-kit-to-profile.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from "fs" 2 | 3 | //Description: Adds the .kenv bin dir to your $PATH 4 | export {} 5 | 6 | // if windows 7 | if (process.platform === "win32") { 8 | await div(`## To add .kit/bin to your path on Windows 9 | 10 | 1. Open the Start Search, type in "env", and choose "Edit the system environment variables" 11 | 2. Click the "Environment Variables..." button. 12 | 3. Under the "System Variables" section, scroll down and highlight the "Path" variable, then click the "Edit..." button. 13 | 4. In the next screen, click "New" and then "Browse" to find the ${kitPath( 14 | "bin" 15 | )} directory. 16 | 5. Click "OK" to close the dialogs. 17 | 6. Close the terminal and open it again. 18 | `) 19 | 20 | exit() 21 | } 22 | 23 | let exportKitBinPath = `export PATH="$PATH:${kitPath( 24 | "bin" 25 | )}"` 26 | 27 | let choices = [ 28 | `.zshrc`, 29 | `.bashrc`, 30 | `.bash_profile`, 31 | `.config/fish/config.fish`, 32 | `.profile`, 33 | ] 34 | 35 | let profiles = choices 36 | .map(profile => `${env.HOME}/${profile}`) 37 | .filter(profile => existsSync(profile)) 38 | 39 | let selectedProfile = await arg( 40 | "Select your profile:", 41 | profiles 42 | ) 43 | 44 | await appendFile(selectedProfile, `\n${exportKitBinPath}`) 45 | let { stdout } = execaCommandSync(`wc ${selectedProfile}`) 46 | let lineCount = stdout.trim().split(" ").shift() 47 | 48 | await edit(selectedProfile, kenvPath(), lineCount) 49 | -------------------------------------------------------------------------------- /src/globals/custom.ts: -------------------------------------------------------------------------------- 1 | import { readFile, writeFile } from "node:fs/promises" 2 | import { ensureFile, pathExists, readJson, writeJson } from "./fs-extra.js" 3 | 4 | type ReadFileOptions = Parameters[1] 5 | 6 | export type EnsureReadFile = (path: string, defaultContent?: string, options?: ReadFileOptions) => Promise 7 | 8 | export type EnsureReadJson = 9 | (path: string, defaultContent: T, options?: Parameters[1]) => Promise 10 | 11 | export let ensureReadFile = async ( 12 | pathLike: string, 13 | defaultContent = "", 14 | options = { encoding: "utf-8" } as Parameters[1] 15 | ) => { 16 | await ensureFile(pathLike) 17 | if (defaultContent) { 18 | let readContent = await readFile(pathLike, options) 19 | if (!readContent) { 20 | await writeFile(pathLike, defaultContent) 21 | return defaultContent 22 | } 23 | } 24 | 25 | return (await readFile(pathLike, options)) as string 26 | } 27 | 28 | export let ensureReadJson = async ( 29 | pathLike: string, 30 | defaultContent: T, 31 | options?: Parameters[1] 32 | ): Promise => { 33 | if (await pathExists(pathLike)) { 34 | return await readJson(pathLike, options) 35 | } 36 | 37 | await ensureFile(pathLike) 38 | await writeJson(pathLike, defaultContent) 39 | return defaultContent 40 | } 41 | 42 | global.ensureReadFile = ensureReadFile 43 | global.ensureReadJson = ensureReadJson 44 | -------------------------------------------------------------------------------- /src/main/sponsor.ts: -------------------------------------------------------------------------------- 1 | // Name: Sponsor to Unlock Script Kit Pro 2 | // NameHTML: 3 | // Description: Unlock Debugger, Logger, and Support Development 4 | // Enter: Open Sponsorship Page 5 | // PreviewPath: $KIT/SPONSOR.md 6 | // FocusedClassName: shadow-md shadow-primary/25 bg-gradient-to-r from-transparent to-primary-50 7 | // Index: 0 8 | 9 | let sponsorUrl = `https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205` 10 | try { 11 | sponsorUrl = (await readFile(kitPath('data', 'sponsor-url.txt'), 'utf-8')).trim() 12 | } catch (error) { 13 | warn(`Failed to read sponsor-url.txt`) 14 | } 15 | try { 16 | sponsorUrl = (await readFile(kitPath('data', 'sponsor-url.txt'), 'utf-8')).trim() 17 | } catch (error) { 18 | warn(`Failed to read sponsor-url.txt`) 19 | } 20 | 21 | open(sponsorUrl) 22 | 23 | export {} 24 | -------------------------------------------------------------------------------- /src/core/constants.ts: -------------------------------------------------------------------------------- 1 | import { home, kenvPath, kitPath } from "./resolvers.js" 2 | import { isMac } from "./is.js" 3 | 4 | export let cmd = isMac ? "cmd" : "ctrl" 5 | export let returnOrEnter = isMac ? "return" : "enter" 6 | 7 | export const scriptsDbPath = kitPath("db", "scripts.json") 8 | export const timestampsPath = kitPath("db", "timestamps.json") 9 | export const statsPath = kitPath("db", "stats.json") 10 | export const prefsPath = kitPath("db", "prefs.json") 11 | export const promptDbPath = kitPath("db", "prompt.json") 12 | export const themeDbPath = kitPath("db", "theme.json") 13 | export const userDbPath = kitPath("db", "user.json") 14 | export const tmpClipboardDir = kitPath("tmp", "clipboard") 15 | export const tmpDownloadsDir = kitPath("tmp", "downloads") 16 | 17 | export const getMainScriptPath = () => { 18 | const version = process.env?.KIT_MAIN_SCRIPT 19 | return kitPath("main", `index${version ? `-${version}` : ""}.js`) 20 | } 21 | 22 | export const kitDocsPath = home(".kit-docs") 23 | 24 | export const KENV_SCRIPTS = kenvPath("scripts") 25 | export const KENV_APP = kenvPath("app") 26 | export const KENV_BIN = kenvPath("bin") 27 | 28 | export const KIT_APP = kitPath("run", "app.js") 29 | export const KIT_APP_PROMPT = kitPath("run", "app-prompt.js") 30 | export const KIT_APP_INDEX = kitPath("run", "app-index.js") 31 | 32 | export const SHELL_TOOLS = [ 33 | "bash", 34 | "sh", 35 | "zsh", 36 | "fish", 37 | "powershell", 38 | "pwsh", 39 | "cmd" 40 | ] 41 | -------------------------------------------------------------------------------- /src/core/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { homedir } from "node:os" 2 | import * as path from "node:path" 3 | 4 | const windowsSlashRE = /\\/g 5 | export function slash(p: string): string { 6 | return p.replace(windowsSlashRE, "/") 7 | } 8 | 9 | export let createPathResolver = 10 | (parentDir: string) => 11 | (...parts: string[]) => { 12 | return path.resolve(parentDir, ...parts) 13 | } 14 | 15 | export let home = (...pathParts: string[]) => { 16 | return createPathResolver(homedir())(...pathParts) 17 | } 18 | 19 | const getEnvOrDefault = ( 20 | envVar: string | undefined, 21 | defaultValue: string 22 | ): string => { 23 | return envVar && envVar !== "undefined" 24 | ? envVar 25 | : defaultValue 26 | } 27 | 28 | export let kitPath = (...parts: string[]) => 29 | createPathResolver( 30 | getEnvOrDefault(process.env.KIT, home(".kit")) 31 | )(...parts.filter(Boolean)) 32 | 33 | export let kenvPath = (...parts: string[]) => { 34 | return createPathResolver( 35 | getEnvOrDefault(process.env.KENV, home(".kenv")) 36 | )(...parts.filter(Boolean)) 37 | } 38 | 39 | export let kitPnpmPath = (...parts: string[]) => { 40 | return createPathResolver( 41 | getEnvOrDefault(process.env.KIT_PNPM_PATH, kitPath()) 42 | )(...parts.filter(Boolean)) 43 | } 44 | 45 | export let kitDotEnvPath = () => { 46 | return createPathResolver( 47 | getEnvOrDefault( 48 | process.env.KIT_DOTENV_PATH, 49 | kenvPath(".env") 50 | ) 51 | )() 52 | } 53 | -------------------------------------------------------------------------------- /src/cli/stream-deck.ts: -------------------------------------------------------------------------------- 1 | //Menu: Prepare Script for Stream Deck 2 | //Description: Creates a .sh file around a script 3 | //Author: John Lindquist 4 | //Twitter: @johnlindquist 5 | 6 | let createCommand = ( 7 | launchApp: boolean, 8 | scriptPath: string 9 | ) => 10 | launchApp 11 | ? `~/.kit/kar ${scriptPath}` 12 | : `~/.kit/script ${scriptPath}` 13 | 14 | let { command, filePath } = await selectScript( 15 | "Prepare which script for Stream Deck?" 16 | ) 17 | 18 | let launchApp = await arg("Run the script:", [ 19 | { 20 | name: "with the prompt", 21 | value: true, 22 | description: ".sh that opens the prompt", 23 | }, 24 | { 25 | name: "no prompt", 26 | value: false, 27 | description: ".sh that runs in the background", 28 | }, 29 | ]) 30 | 31 | let binPath = kenvPath("deck", command + ".sh") 32 | await ensureDir(path.dirname(binPath)) 33 | await writeFile(binPath, createCommand(launchApp, filePath)) 34 | chmod(755, binPath) 35 | let resolvedPath = path.resolve(binPath) 36 | copy(resolvedPath) 37 | 38 | let info = ` 39 | 40 |
41 | "${resolvedPath}" copied to clipboard 42 |
43 | 44 | * Hit "Enter" to launch Stream Deck 45 | * Hit "Escape" to exit 46 | 47 | **Create a System->Open action and paste here:** 48 | 49 | ![Stream Deck Setup](${kitPath( 50 | "images", 51 | "stream-deck.png" 52 | )}) 53 | ` 54 | 55 | await div(await highlight(info)) 56 | 57 | openApp("Elgato Stream Deck.app") 58 | 59 | export {} 60 | -------------------------------------------------------------------------------- /src/cli/share-script-as-discussion.ts: -------------------------------------------------------------------------------- 1 | //Menu: Share Script for Kit Discussion 2 | //Description: Create a gist and copy discussion content to clipboard 3 | 4 | import { authenticate } from "../api/kit.js" 5 | 6 | let { filePath, command } = await selectScript( 7 | `Share which script?` 8 | ) 9 | 10 | div(md(`## Creating Gist...`)) 11 | setLoading(true) 12 | 13 | let octokit = await authenticate() 14 | 15 | let fileBasename = path.basename(filePath) 16 | const fileExtension = path.extname(filePath).replace('.', '') 17 | 18 | let content = await readFile(filePath, "utf8") 19 | let response = await octokit.rest.gists.create({ 20 | files: { 21 | [fileBasename]: { 22 | content: await readFile(filePath, "utf8"), 23 | }, 24 | }, 25 | public: true, 26 | }) 27 | 28 | const mdLanguage = ['js', 'jsx', 'ts', 'tsx'].includes(fileExtension) ? fileExtension : 'js' 29 | 30 | let gistUrl = response.data.files[fileBasename].raw_url 31 | 32 | let link = `https://scriptkit.com/api/new?name=${command}&url=${gistUrl}"` 33 | 34 | let discussionPost = `[Open ${command} in Script Kit](${link}) 35 | 36 | \`\`\`${mdLanguage} 37 | ${content} 38 | \`\`\` 39 | ` 40 | 41 | copy(discussionPost) 42 | 43 | open( 44 | "https://github.com/johnlindquist/kit/discussions/new?category=share" 45 | ) 46 | 47 | let message = `Copied ${command} to clipboard as markdown` 48 | 49 | setAlwaysOnTop(true) 50 | await div( 51 | await highlight(`## ${message} 52 | 53 | ${discussionPost}`) 54 | ) 55 | 56 | export {} 57 | -------------------------------------------------------------------------------- /src/main/edit.ts: -------------------------------------------------------------------------------- 1 | // Name: Edit Menu 2 | // Description: Select a script then edit action. 3 | // Exclude: true 4 | 5 | import { Choice } from "../types/core" 6 | import { CLI } from "../cli" 7 | 8 | let { command, filePath } = await selectScript( 9 | `Edit script:` 10 | ) 11 | 12 | let editActions: Choice[] = [ 13 | { 14 | name: "Open", 15 | description: `Open ${command}${ 16 | env.KIT_EDITOR ? ` in ${env.KIT_EDITOR}` : `` 17 | }`, 18 | value: "edit", 19 | }, 20 | { 21 | name: "Duplicate", 22 | description: `Make a copy of ${command} and open${ 23 | env.KIT_EDITOR ? ` in ${env.KIT_EDITOR}` : `` 24 | }`, 25 | value: "duplicate", 26 | }, 27 | { 28 | name: "Rename", 29 | description: `Prompt to rename ${command}`, 30 | value: "rename", 31 | }, 32 | { 33 | name: "Remove", 34 | description: `Delete ${command} to trash`, 35 | value: "remove", 36 | }, 37 | 38 | { 39 | name: `Open ${command}.log`, 40 | description: `Opens ${command}.log in your editor`, 41 | value: "open-command-log", 42 | }, 43 | ] 44 | 45 | let kenvDirs = (await readdir(kenvPath("kenvs"))) || [] 46 | if (kenvDirs.length) { 47 | editActions.splice(4, 0, { 48 | name: "Move", 49 | description: `Move ${command} to a selected kenv`, 50 | value: "move", 51 | }) 52 | } 53 | 54 | let editAction = await arg( 55 | "Which action?", 56 | editActions 57 | ) 58 | await cli(editAction, filePath) 59 | 60 | export {} 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Script Kit v3 2 | 3 | [https://scriptkit.com/](https://scriptkit.com/) 4 | 5 | ## Join the Discussion 6 | 7 | [https://github.com/johnlindquist/kit/discussions](https://github.com/johnlindquist/kit/discussions) 8 | 9 | ## Docs 10 | 11 | [https://github.com/johnlindquist/kit-docs](https://github.com/johnlindquist/kit-docs) 12 | 13 | ## ⭐️ Unlock Script Kit Pro by Sponsoring Script Kit ⭐️ 14 | 15 | ❤️ [Sponsor me on GitHub](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205) ❤️ 16 | 17 | ### Sponsor Only Features 18 | 19 | | Shipped | Planned | 20 | | --- | --- | 21 | | Unlimited Active Prompts | Sync Scripts to GitHub Repo | 22 | | Built-in Debugger | Run Script Remotely as GitHub Actions | 23 | | Script Log Window | Advanced Screenshots | 24 | | Vite Widgets | Screen Recording | 25 | | Webcam Capture | Measure Tool | 26 | | Desktop Color Picker | Debug from IDE | 27 | | Basic Screenshots | 28 | 29 | ## Prerequisites 30 | 31 | Install pnpm: 32 | 33 | [https://pnpm.io/installation](https://pnpm.io/installation) 34 | 35 | ### Clone Kit SDK 36 | 37 | Clone and install: 38 | ``` 39 | git clone https://github.com/johnlindquist/kit.git 40 | cd kit 41 | pnpm install 42 | ``` 43 | 44 | 45 | ### Building Kit SDK 46 | 47 | `pnpm build` 48 | 49 | The build command builds the SDK to ~/.kit 50 | 51 | #### pnpm link to app (First run only) 52 | 53 | 1. cd to ~/.kit 54 | 2. pnpm link 55 | 3. cd to wherever you cloned kitapp 56 | 4. pnpm link @johnlindquist/kit 57 | -------------------------------------------------------------------------------- /src/api/npm.test.ts: -------------------------------------------------------------------------------- 1 | import ava from "ava" 2 | 3 | import tmp from "tmp-promise" 4 | import { randomUUID } from "node:crypto" 5 | import { join } from "node:path" 6 | await import("../api/global.js") 7 | await import("../api/kit.js") 8 | await import("../api/pro.js") 9 | await import("../api/lib.js") 10 | await import("../platform/base.js") 11 | await import("../target/terminal.js") 12 | 13 | import { kenvPath } from "../core/utils.js" 14 | 15 | // biome-ignore lint/suspicious/useAwait: 16 | await tmp.withDir(async (dir) => { 17 | process.env.KENV = dir.path 18 | process.env.KIT_CONTEXT = "workflow" 19 | process.env.KENV = path.resolve(dir.path, ".kenv") 20 | 21 | ava.beforeEach(async (t) => { 22 | global.kitScript = `${randomUUID()}.js` 23 | global.__kitDbMap = new Map() 24 | 25 | await ensureDir(kenvPath()) 26 | await ensureDir(kitPath()) 27 | 28 | 29 | t.log({ 30 | kenvPath: kenvPath(), 31 | kitPath: kitPath() 32 | }) 33 | }) 34 | 35 | ava("legacy npm import with title-case", async (t) => { 36 | try{ 37 | await exec(`pnpm init`, { 38 | cwd: kenvPath() 39 | }) 40 | await exec(`pnpm init`, { 41 | cwd: kitPath() 42 | }) 43 | } catch (error) { 44 | t.log(error) 45 | } 46 | console.log = t.log 47 | global.log = t.log 48 | flag.trust = true 49 | args.push("hello") 50 | let { titleCase } = await npm("title-case") 51 | let result = titleCase(await arg("Enter a string to title case:")) 52 | 53 | t.is(result, "Hello") 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /src/cli/add-kenv-to-profile.ts: -------------------------------------------------------------------------------- 1 | import { existsSync } from "fs" 2 | 3 | //Description: Adds the .kenv bin dir to your $PATH 4 | export {} 5 | 6 | let kenv = await selectKenv() 7 | 8 | if (process.platform === "win32") { 9 | await div(`## To add .kenv/bin to your path on Windows 10 | 11 | 1. Open the Start Search, type in "env", and choose "Edit the system environment variables" 12 | 2. Click the "Environment Variables..." button. 13 | 3. Under the "System Variables" section, scroll down and highlight the "Path" variable, then click the "Edit..." button. 14 | 4. In the next screen, click "New" and then "Browse" to find the ${kenvPath( 15 | "bint" 16 | )} directory. 17 | 5. Click "OK" to close the dialogs. 18 | 6. Close the terminal and open it again. 19 | `) 20 | 21 | exit() 22 | } 23 | 24 | let exportKenvPath = `export PATH="$PATH:${path.resolve( 25 | kenv.dirPath, 26 | "bin" 27 | )}"` 28 | 29 | let choices = [ 30 | `.zshrc`, 31 | `.bashrc`, 32 | `.bash_profile`, 33 | `.config/fish/config.fish`, 34 | `.profile`, 35 | ] 36 | 37 | let profiles = choices 38 | .map(profile => `${env.HOME}/${profile}`) 39 | .filter(profile => existsSync(profile)) 40 | 41 | let selectedProfile = await arg( 42 | "Select your profile:", 43 | profiles 44 | ) 45 | 46 | await appendFile(selectedProfile, `\n${exportKenvPath}`) 47 | let { stdout } = execaCommandSync(`wc ${selectedProfile}`) 48 | let lineCount = stdout.trim().split(" ").shift() 49 | 50 | await edit(selectedProfile, kenvPath(), lineCount) 51 | -------------------------------------------------------------------------------- /src/cli/rename.ts: -------------------------------------------------------------------------------- 1 | // Description: Rename Script 2 | 3 | import { refreshScripts } from "../core/db.js" 4 | import { 5 | checkIfCommandExists, 6 | extensionRegex, 7 | trashScriptBin, 8 | } from "../core/utils.js" 9 | 10 | import { default as generate } from "project-name-generator" 11 | 12 | let examples = Array.from({ length: 3 }) 13 | .map((_, i) => generate({ words: 2 }).dashed) 14 | .join(", ") 15 | 16 | import { Script } from "../types/core.js" 17 | 18 | let script: Script = await selectScript( 19 | `Which script do you want to rename?` 20 | ) 21 | 22 | let { filePath } = script 23 | 24 | let scriptExtension = path.extname(filePath) 25 | 26 | let newCommand = await arg( 27 | { 28 | description: `Rename ${filePath}`, 29 | placeholder: `Enter the new script name:`, 30 | validate: checkIfCommandExists, 31 | strict: false, 32 | }, 33 | [ 34 | { 35 | info: true, 36 | name: `Requirements: lowercase, dashed, no extension`, 37 | description: `Examples: ${examples}`, 38 | }, 39 | ] 40 | ) 41 | 42 | let lenientCommand = newCommand.replace(extensionRegex, "") 43 | 44 | let newFilePath = path.resolve( 45 | path.dirname(filePath), 46 | lenientCommand + scriptExtension 47 | ) 48 | 49 | try { 50 | await trashScriptBin(script) 51 | } catch (error) { 52 | warn(error) 53 | } 54 | 55 | mv(filePath, newFilePath) 56 | await cli("create-bin", "scripts", newFilePath) 57 | await refreshScripts() 58 | 59 | await edit(newFilePath, kenvPath()) 60 | 61 | export {} 62 | -------------------------------------------------------------------------------- /src/core/snippets.test.ts: -------------------------------------------------------------------------------- 1 | import ava from "ava" 2 | import { getSnippet } from "./snippets" 3 | 4 | ava("getSnippet - basic metadata and snippet", (t) => { 5 | const content = ` 6 | // Name: Test Snippet 7 | // Tag: test 8 | This is a test snippet 9 | with multiple lines 10 | `.trim() 11 | const result = getSnippet(content) 12 | 13 | t.log({ result }) 14 | 15 | t.deepEqual(result.metadata, { name: "Test Snippet", tag: "test" }) 16 | t.is(result.snippet.trim(), "This is a test snippet\nwith multiple lines") 17 | }) 18 | 19 | ava("getSnippet - no metadata", (t) => { 20 | const content = "This is a snippet without metadata" 21 | const result = getSnippet(content) 22 | 23 | t.deepEqual(result.metadata, {}) 24 | t.is(result.snippet.trim(), "This is a snippet without metadata") 25 | }) 26 | 27 | ava("getSnippet - snippet with metadata-like content", (t) => { 28 | const content = ` 29 | // Name: Tricky Snippet 30 | // Tag: tricky 31 | This is a snippet 32 | // This line looks like metadata but isn't 33 | # This one too 34 | `.trim() 35 | const result = getSnippet(content) 36 | 37 | t.log({ result }) 38 | 39 | t.deepEqual(result.metadata, { name: "Tricky Snippet", tag: "tricky" }) 40 | t.is( 41 | result.snippet.trim(), 42 | `This is a snippet 43 | // This line looks like metadata but isn't 44 | # This one too` 45 | ) 46 | }) 47 | 48 | ava("getSnippet - empty content", (t) => { 49 | const content = "" 50 | const result = getSnippet(content) 51 | 52 | t.deepEqual(result.metadata, {}) 53 | t.is(result.snippet, "") 54 | }) 55 | -------------------------------------------------------------------------------- /src/cli/share.ts: -------------------------------------------------------------------------------- 1 | // Name: Share Script 2 | // Description: Open the Share Menu 3 | 4 | import { CLI } from "../cli" 5 | import { Script } from "../types" 6 | 7 | let { filePath } = (await selectScript({ 8 | placeholder: `Share which script?`, 9 | enter: "Open the Share Menu", 10 | })) as Script 11 | 12 | let how: keyof CLI = await arg( 13 | { 14 | placeholder: "Share", 15 | resize: true, 16 | }, 17 | [ 18 | { 19 | name: "Create Github Discussion", 20 | description: 21 | "Post script as gist and open Github discussion in browser", 22 | value: "share-script-as-discussion", 23 | }, 24 | { 25 | name: "Copy", 26 | description: "Copy the script to the clipboard", 27 | value: "share-copy", 28 | }, 29 | { 30 | name: "Copy as Markdown", 31 | description: 32 | "Copies script contents in fenced JS Markdown", 33 | value: "share-script-as-markdown", 34 | }, 35 | { 36 | name: "Post as a gist", 37 | description: "Post the script as a gist", 38 | value: "share-script", 39 | }, 40 | { 41 | name: "Create Install URL", 42 | description: 43 | "Create a link which will install the script", 44 | value: "share-script-as-link", 45 | }, 46 | { 47 | name: "Share as kit:// link", 48 | description: 49 | "Create a link which will install the script", 50 | value: "share-script-as-kit-link", 51 | }, 52 | ] 53 | ) 54 | 55 | await cli(how, filePath) 56 | 57 | export {} 58 | -------------------------------------------------------------------------------- /src/cli/create-all-bins-no-trash.ts: -------------------------------------------------------------------------------- 1 | import { getScripts } from "../core/db.js" 2 | import { getKenvs } from "../core/utils.js" 3 | 4 | let scripts = await getScripts(false) 5 | 6 | let kenvs = await getKenvs() 7 | 8 | log(`🔎 Found kenvs`, kenvs) 9 | 10 | await ensureDir(kenvPath("bin")) 11 | 12 | for await (let kenv of kenvs) { 13 | await ensureDir(path.resolve(kenv, "bin")) 14 | } 15 | 16 | let jsh = process.env?.SHELL?.includes("jsh") 17 | let template = jsh ? "stackblitz" : "terminal" 18 | let useCmd = 19 | process.platform === "win32" && !process.env?.KIT_WSL 20 | 21 | if (useCmd) { 22 | template = "cmd" 23 | } 24 | let binTemplate = await readFile( 25 | kitPath("templates", "bin", template), 26 | "utf8" 27 | ) 28 | 29 | let binTemplateCompiler = compile(binTemplate) 30 | 31 | try { 32 | for await (let { command, filePath } of scripts) { 33 | let compiledBinTemplate = binTemplateCompiler({ 34 | command, 35 | type: "scripts", 36 | KIT: kitPath(), 37 | KIT_NODE_PATH: process.env.KIT_NODE_PATH, 38 | ...global.env, 39 | TARGET_PATH: filePath, 40 | }) 41 | 42 | let binDirPath = path.resolve( 43 | filePath, 44 | "..", 45 | "..", 46 | ...(jsh ? ["node_modules", ".bin"] : ["bin"]) 47 | ) 48 | let binFilePath = path.resolve(binDirPath, command) 49 | if (useCmd) { 50 | binFilePath += ".cmd" 51 | } 52 | await global.writeFile(binFilePath, compiledBinTemplate) 53 | global.chmod(755, binFilePath) 54 | } 55 | } catch (error) { 56 | log(`🚨 Error creating bins`, error) 57 | } 58 | 59 | export {} 60 | -------------------------------------------------------------------------------- /root/bin/kit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -f ~/.kenv/.env ]; then 4 | source ~/.kenv/.env 5 | fi 6 | 7 | KIT=$(cd "$(dirname $(readlink -f ${BASH_SOURCE[0]}))/.." &>/dev/null && pwd) 8 | # Check if "kit" is in node_modules/.bin 9 | [ -L $BASH_SOURCE ] && 10 | [[ $BASH_SOURCE =~ "node_modules" ]] && 11 | KIT="$( 12 | cd "$(dirname "$0")/.." 13 | pwd 14 | )/@johnlindquist/kit" && 15 | KENV="$( 16 | cd "$(dirname "$0")/../.." 17 | pwd 18 | )" 19 | 20 | # Change to KIT directory before attempting "pnpm node" or else you run into ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND 21 | cd "$KIT" || exit 1 22 | 23 | # Only set KIT_NODE_PATH if it's not already set 24 | if [ -z "$KIT_NODE_PATH" ]; then 25 | KIT_NODE_PATH="$($KIT/node_modules/.bin/pnpm node -p "process.execPath" 2>/dev/null || $KIT/pnpm node -p "process.execPath" 2>/dev/null || pnpm node -p "process.execPath" 2>/dev/null || node -p "process.execPath" 2>/dev/null)" 26 | if ! command -v "$KIT_NODE_PATH" &> /dev/null; then 27 | KIT_NODE_PATH=$(which node) 28 | if [ -z "$KIT_NODE_PATH" ]; then 29 | echo "Error: Node.js not found in PATH. Provide a KIT_NODE_PATH in your environment." >&2 30 | exit 1 31 | fi 32 | fi 33 | fi 34 | 35 | # Change back to the original directory 36 | cd - > /dev/null || exit 1 37 | 38 | if [[ $KIT =~ ^/[a-zA-Z]/ ]]; then 39 | drive_letter=${KIT:1:1} 40 | KIT="${KIT/\/$drive_letter\//\/$drive_letter:/}" 41 | fi 42 | 43 | KIT_TARGET="terminal" \ 44 | KIT="$KIT" \ 45 | KENV="$KENV" \ 46 | NODE_NO_WARNINGS=1 \ 47 | "$KIT_NODE_PATH" \ 48 | --loader "file://$KIT/build/loader.js" \ 49 | "$KIT/run/terminal.js" \ 50 | "$@" -------------------------------------------------------------------------------- /src/cli/kenv-push.ts: -------------------------------------------------------------------------------- 1 | // Description: Git Push Kenv Repo 2 | 3 | import { 4 | getKenvs, 5 | getShellSeparator, 6 | } from "../core/utils.js" 7 | 8 | let kenvs = (await getKenvs()).map(value => ({ 9 | name: path.basename(value), 10 | value, 11 | })) 12 | 13 | kenvs.unshift({ 14 | name: "main", 15 | value: kenvPath(), 16 | }) 17 | 18 | let dir = await arg("Push which kenv", kenvs) 19 | 20 | cd(dir) 21 | 22 | let shellSep = getShellSeparator() 23 | 24 | await term({ 25 | command: `git status`, 26 | cwd: dir, 27 | shortcuts: [ 28 | { 29 | name: "Add, Commit, Push", 30 | key: `${cmd}+p`, 31 | bar: "left", 32 | onPress: async () => { 33 | term.write( 34 | `git add . ${shellSep} git commit -m "pushed from Script Kit" ${shellSep} git push` 35 | ) 36 | }, 37 | }, 38 | { 39 | name: "Add All", 40 | key: `${cmd}+g`, 41 | bar: "right", 42 | onPress: async () => { 43 | term.write("git add .") 44 | }, 45 | }, 46 | { 47 | name: "Commit", 48 | key: `${cmd}+i`, 49 | bar: "right", 50 | onPress: async () => { 51 | term.write(`git commit -m "pushed from Script Kit"`) 52 | }, 53 | }, 54 | { 55 | name: "Push", 56 | key: `${cmd}+t`, 57 | bar: "right", 58 | onPress: async () => { 59 | term.write(`git push`) 60 | }, 61 | }, 62 | { 63 | name: "Exit", 64 | key: `${cmd}+w`, 65 | bar: "right", 66 | onPress: async () => { 67 | submit("") 68 | }, 69 | }, 70 | ], 71 | }) 72 | 73 | await mainScript() 74 | 75 | // Prompt if stash exists to re-apply changes 76 | 77 | export {} 78 | --------------------------------------------------------------------------------