diff --git a/.gitignore b/.gitignore
index ccf689d..ddc2d6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@ apps/*/public/robots.txt
apps/*/test-results
.eslintcache
+.env*.local
+.vscode
\ No newline at end of file
diff --git a/apps/scriptkit/package.json b/apps/scriptkit/package.json
index 813eac0..b9cf425 100644
--- a/apps/scriptkit/package.json
+++ b/apps/scriptkit/package.json
@@ -55,7 +55,7 @@
"@code-hike/mdx": "^0.8.2",
"@headlessui/react": "^1.7.1",
"@heroicons/react": "=1.0.6",
- "@johnlindquist/kit": "0.75.3",
+ "@johnlindquist/kit": "2.0.4",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/mdx": "^2.3.0",
"@mdx-js/react": "^2.3.0",
diff --git a/apps/scriptkit/public/data/announcements.json b/apps/scriptkit/public/data/announcements.json
index 1b9fabe..43a6378 100644
--- a/apps/scriptkit/public/data/announcements.json
+++ b/apps/scriptkit/public/data/announcements.json
@@ -1 +1 @@
-[{"group":"Favorite","name":"Group Choices","cache":"true","pass":"true","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1289","url":"","title":"Script Kit 1.76.10 - July 2023 Release - Searching Enhancements, Grouping Logic, More Choice Options","command":"script-kit-17610-july-2023-release-searching-enhancements-grouping-logic-more-choice-options","content":"# Script Kit 1.76.10 - July 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist\r\n\r\n## Features\r\n\r\n### Windows arm64 Build\r\n\r\nWe now have \"arm64\" builds for Windows: https://github.com/johnlindquist/kitapp/releases/tag/v1.76.10\r\n\r\n> arm64 Windows limitations: Unfortunately, the \"keyboard\" libraries for Windows are not yet available for arm64. So, you won't be able to use snippets, \"setSelectedText\", and other keyboard-related features. I'd recommend AutoHotkey as a workaround until the libraries are available.\r\n\r\n### Scripts Caching\r\n\r\nInstead of waiting for the main menu to parse/load the scripts, the previous scripts results are now cached. This shaves off the ~100ms delay when opening the main menu from the keyboard shortcut. (There are also some clever technical tricks under the hood that would require a blog post to explain)\r\n\r\n> Note: If you have a script that uses a shortcut which displays choices that don't change often, consider adding the `// Cache: true` metadata to the script for that extra 100ms boost.\r\n\r\n\r\n### Scripts Grouping and `groupChoices`\r\n\r\nUse the `//Group` metadata to group scripts together in the main menu. For example:\r\n\r\n```js\r\n// Group: Favorite\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png)\r\n\r\nThe you can search for \"Favorite\" from the menu to filter to the scripts with that metadata.\r\n\r\n\r\nYou can also add the `.group` property to any choice, then pass your choices to `groupChoices` to use the grouping behavior in your own scripts.\r\n\r\n```js\r\n// Name: Group Choices\r\n// Cache: true\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { faker } from \"@faker-js/faker\"\r\nimport _ from \"lodash\"\r\n\r\nlet faang = [\"Facebook\", \"Apple\", \"Amazon\", \"Netflix\", \"Google\"]\r\n\r\nlet people = Array.from({ length: 25 }).map(() => {\r\n let name = faker.person.fullName()\r\n return {\r\n name,\r\n value: name,\r\n description: faker.color.human(),\r\n group: _.sample(faang), // Grouping by a random company\r\n }\r\n})\r\n\r\nlet groupedChoices = groupChoices(people)\r\n\r\nlet result = await mini(\"Arg Group Demo\", groupedChoices)\r\nawait editor(result)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png)\r\n\r\n### Flags \"Submenu\"\r\n\r\nWhen using `flags`, pressing \"right\" (or `cmd+k`) now displays the available actions (AKA \"flags\") to the side in a separate searchable list with its own state.\r\n\r\nThis is a first pass at a \"submenu\" feature. We'll explore nested submenus in the future.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png)\r\n\r\n### Searching Off-loaded to Main Process\r\n\r\nPreviously, the searching logic was done in the renderer process. This creating some technical limitations on how we could pass results from a script to the list.\r\n\r\nNow, you will be able pass \"scored\" choice results directly from the script to the list enabling you move the search logic to your script/server/API and display highlighted \"scored\" choices in the list. More information on this in the future.\r\n\r\n### `// Pass` and `.pass` Metadata\r\n\r\nThe `// Pass` metadata on a script (and `.pass` property on a choice) will pass the current input of the prompt directly to the script so you can type input from the main menu that will be passed to the script.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png)\r\n\r\n```js\r\n// Name: Grocery List\r\n// Pass: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet note = arg?.pass || (await arg(\"Enter an item\"))\r\n\r\nawait editor(note)\r\n```\r\n\r\nYou can also do this with your own choices by using the `.pass` property on a choice and using the `groupChoices` (as discussed above) to bring up the more advanced list.\r\n\r\n### Choice `.miss` Property\r\n\r\nAdding `.miss` to a choice will make the choice only appear if the search doesn't match any other choices. This is useful for showing a \"No Results\" choice:\r\n\r\n```js\r\n// Name: Miss Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait mini(\"Select an Item\", [\r\n \"One\",\r\n \"Two\",\r\n {\r\n name: \"No Choices Available\",\r\n miss: true,\r\n disableSubmit: true,\r\n nameClassName: `text-red-500`,\r\n },\r\n])\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png)\r\n\r\n## Choice `.skip` Property\r\n\r\nUsing `.skip` allows you to create a choice that can't be searched/selected:\r\n\r\n> Note: This is used internally for the \"groupChoice()\" headers\r\n\r\n```js\r\n// Name: Separator Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"A demo for skip\", [\r\n \"Apple\",\r\n \"Banana\",\r\n \"Cherry\",\r\n {\r\n name: \"Separator\",\r\n height: PROMPT.ITEM.HEIGHT.XXXS,\r\n html: `
`,\r\n skip: true,\r\n },\r\n \"Celery\",\r\n \"Cucumber\",\r\n \"Lettuce\",\r\n])\r\n```\r\n\r\n## Thanks for Using Script Kit!\r\n\r\nHappy Scripting!\r\n\r\n\\- John Lindquist","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-28T00:11:40Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1256","url":"","title":"Script Kit 1.59.1 - May 2023 Release","command":"script-kit-1591-may-2023-release","content":"# Script Kit 1.59.1 - May 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Sunsetting Features\r\n\r\nWe're \"sunsetting\"/not supporting a few features going forward. \r\n\r\n> The `npm` and `db` apis will still be available (they're used internally in the SDK!), but we won't be actively supporting them\r\n\r\n1. `await npm()`. Use `await import()` or regular `import` statements instead.\r\n\r\nScript Kit now recommends using standard import methods. When you attempt to run a script and an import fails, Script Kit will catch the error and prompt you to install it. This removes the need for `await npm()`. This is especially useful for people using TypeScript as you don't need to worry about typing `npm` anymore.\r\n\r\n2. `await db()`. We now recommend `keyv`: [https://www.npmjs.com/package/keyv](https://www.npmjs.com/package/keyv)\r\n\r\n`keyv` is a much better solution for storing/retrieving data and achieves all of the future features we had planned for `db`.\r\n\r\nWe'll do our best to update older demos/tutorials that use these methods as we're able.\r\n\r\n## Previews Everywhere\r\n\r\nEvery prompt type (`term`, `drop`, `fields`, etc) now supports the `preview` key. Pass in HTML to display it to the right side of your prompt. This is great for instructions and guiding the user through each script:\r\n\r\n> Note: The `md()` helper is often used to transform markdown into HTML\r\n\r\n\r\n```js\r\nawait term({\r\n preview: md(`# Follow these steps:\r\n\r\n1. First, type \\`ls\\` to see the files in this directory\r\n2. Then, type \\`cat \\` to see the contents of a file\r\n3. Finally, type \\`exit\\` to exit the terminal\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n```js\r\nawait drop({\r\n placeholder: \"Drop an mp3\",\r\n preview: md(`# Convert mp3 to wav\r\n\r\n## Instructions\r\n\r\n1. Drag and drop an mp3 file\r\n2. A progress prompt will open then close when ready\r\n3. Your .wav will be created next to the .mp3 file\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n## `term.write()`\r\n\r\nWhen a `term` prompt is open, you can issue terminal commands from shortcuts, etc by using `term.write(myCommand)`\r\n\r\n```js\r\nawait term({\r\n shortcuts: [\r\n {\r\n name: \"List files\",\r\n key: `${cmd}+l`,\r\n onPress: () => {\r\n let command = isWin ? \"dir\" : \"ls\"\r\n term.write(command)\r\n },\r\n bar: \"right\",\r\n },\r\n {\r\n name: \"Up a Directory\",\r\n key: `${cmd}+u`,\r\n onPress: () => {\r\n term.write(\"cd ..\")\r\n },\r\n bar: \"left\",\r\n },\r\n {\r\n name: \"Clear\",\r\n key: `${cmd}+k`,\r\n onPress: () => {\r\n let command = isWin ? \"cls\" : \"clear\"\r\n term.write(command)\r\n },\r\n bar: \"left\",\r\n },\r\n ],\r\n})\r\n```\r\n\r\n\r\n## Kenv Improvements:\r\n\r\n### \"New Kenv\" prompt for GitHub repo:\r\n\r\nWhen creating a new Kenv, it will guide you through the process of linking the Kenv to a remote GitHub repo:\r\n\r\n\r\n\r\n\r\n### With a script selected, take Kenv actions\r\n\r\nPress \"right\" (or `cmd+k`) with a script selected to reveal many \"Kenv\" actions:\r\n\r\n\r\n\r\n### Push/Pull From Remote Kenv\r\n\r\nFrom a script (or a \"Manage Kenv\"), you can now push/pull changes as it swaps you over to a terminal to take action:\r\n\r\n\r\n\r\n### \"Trusted\" Kenvs\r\n\r\nThanks to Script Kit + AI integrations, we've had a large influx of \"non-developer\" users. This necessitated more warnings/protections around sharing scripts.\r\n\r\nSome scripts have features that run scripts automatically: Shortcuts, schedule, background, etc. These kenvs now need to be \"Trusted\" to enable these features to add an extra layer of protection against bad actors. PLEASE only use scripts from people you absolutely trust.\r\n\r\nYou can \"trust\" a kenv during new/clone setup, or later from the Kit tab->Manage Kenv menu. You can also \"untrust\" a kenv from the same menu.\r\n\r\n\r\n\r\n## \"Trigger\" flag\r\n\r\nIf a script is run by \"schedule\", \"shortcut\", etc, you can now access what triggered it by using\r\n\r\n```js\r\nif(flag?.trigger === \"schedule\") // do something specific\r\n```\r\n\r\nThis will allow you customize the behavior based on whether you invoked it manually or automatically\r\n\r\n## Windows Fixes\r\n\r\nHandled edge-cases around\r\n\r\n- Windows setup/install process\r\n- Windows usernames that include spaces\r\n- Windows terminal fixes\r\n\r\nThanks to all the Windows bug reports and testers on Discord. Please keep them coming ❤️\r\n\r\n## Node 18.15.0\r\n\r\nWe're now on node 18.15.0. View the CHANGELOG: https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V18.md#18.15.0\r\n\r\nHappy Scripting - John Lindquist","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-15T18:49:05Z"},{"name":"Transcribe Mic","https":"//github.com/openai/openai-node/issues/77#issuecomment-1463150451","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1178","url":"","title":"Script Kit 1.53.22 - April 2023 Release","command":"script-kit-15322-april-2023-release","content":"# Script Kit 1.53.22 - April 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Features\r\n\r\n### `await mic()`\r\n\r\nUsing await mic will return a buffer of the audio recorded from your microphone.\r\n\r\n```js\r\n// Name: Transcribe Mic\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Configuration, OpenAIApi } = await import(\"openai\")\r\n\r\nlet config = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n})\r\n\r\nlet openai = new OpenAIApi(config)\r\n\r\nlet data = await mic()\r\n\r\nlet stream = Readable.from(data)\r\n// https://github.com/openai/openai-node/issues/77#issuecomment-1463150451\r\nstream.path = \"speech.webm\"\r\n\r\n// If you're confused by \"createTranscription\" params, see: https://github.com/openai/openai-node/issues/93#issuecomment-1471285341\r\nlet response = await openai.createTranscription(stream, \"whisper-1\")\r\n\r\nlet transcriptionPath = tmpPath(`${Date.now()}.txt`)\r\nawait writeFile(transcriptionPath, response.data.text)\r\n\r\nawait editor(response.data.text)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png)\r\n\r\n\r\n### `await webcam()`\r\n\r\nUsing await webcam will return a buffer of the image captured from your webcam.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png)\r\n\r\n```js\r\n// Name: Selfie\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await webcam()\r\n\r\nlet filePath = tmpPath(\"webcam.png\")\r\n\r\nawait writeFile(filePath, buffer)\r\nawait revealFile(filePath)\r\n```\r\n\r\n### Resizing\r\n\r\nThe prompt will now grow and shink to match the length of the list of options.\r\n\r\nYou can also manually control the size of prompts by using the new `PROMPT` constants:\r\n\r\n```js\r\nawait editor({\r\n width: PROMPT.WIDTH[\"5XL\"],\r\n height: PROMPT.HEIGHT[\"5XL\"],\r\n})\r\n```\r\n\r\n### Terminal \"Close on Exit\"\r\n\r\nThe terminal will now automatically close when you exit the command.\r\n\r\n> Note: By default, it start a shell (zsh/bash/cmd.exe for mac/linux/windows), so you'll need to run `&& exit` after your command\r\n\r\n```js\r\nawait term(\"brew install ffmpeg && exit\")\r\n```\r\n\r\nYou can also use the `shell` option to disable the shell and just run the tool directly:\r\n\r\n```js\r\nawait term({\r\n shell: false,\r\n command: \"brew\",\r\n args: [\"services\", \"restart\", \"yabai\"],\r\n closeOnExit: false, // optional, if you want to view output before it closes\r\n})\r\n```\r\n\r\n> Note: Also fixed some resizing bugs with the terminal.\r\n\r\n### Windows App Launcher\r\n\r\nPress `;` from the main menu to launch the App Launcher.\r\n\r\nThanks to @dodgez: https://github.com/johnlindquist/kit/pull/1161\r\n\r\n> Note: We still need a Linux App Launcher if anyone wants to take a stab at it :)\r\n\r\n### Custom Fonts\r\n\r\nYou can now use custom fonts with the UI. In your `~/.kenv/.env` file, add:\r\n\r\n```bash\r\nKIT_MONO_FONT=\"Menlo\"\r\nKIT_SANS_FONT=\"Arial\"\r\n```\r\n\r\n### Experimental: Shebang Scripts 🎉\r\n\r\nAdd a script in your `~/.kenv/scripts` directory with a shebang:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png)\r\n\r\n```bash\r\n#!/bin/bash\r\nopen \"https://scriptkit.com\"\r\n```\r\n\r\nThen run it from the main menu\r\n\r\n### Experimental: Windows `.bat` Files\r\n\r\nMac and Linux users have always had executables they could run in the terminal in the `~/.kenv/bin` dircetory. Now, on Windows, Script Kit will generate `.bat` files associated with each script that you can run from the terminal.\r\n\r\n### Info Choices\r\n\r\nWe now have a custom \"info\" choice that isn't selectable, but shows up in the list to present the user with additional information about the input/list:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png)\r\n\r\n```js\r\n// Name: Testing Info OnNoChoices\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"Select a number\",\r\n [\r\n {\r\n name: \"Sorry, no matches\",\r\n info: \"onNoChoices\", // or \"always\"\r\n },\r\n \"one\",\r\n \"two\",\r\n \"three\",\r\n ]\r\n)\r\n```\r\n\r\n### Removing `npm` requirement\r\n\r\nWhen you run a script with a missing module, it will catch the error and prompt you to install it. This removes the requirement of the `await npm()`, but you can still use it if you find it more convenient.\r\n\r\nThe examples and scripts that I share from now on will no longer use `await npm()`, but I have no plans to deprecate it.\r\n\r\n## Fixes\r\n\r\n- Lots of little improvements around the `chat` component.\r\n- Some solid performance improvements\r\n- Upgrading to lots of the latest libraries\r\n\r\n\r\n## Call for Help: Disable Window Animation on Windows?\r\n\r\nIf anyone knows how to disable the window animation on Windows (the one that transparently zooms in when you open a window), please let me know because I think it's super annoying and I'd love to disable it in the app. For now, I strongly recommend disabling it globally on Windows:\r\n\r\n1. Open the Settings app by pressing the Windows key + I.\r\n2. Click on “Ease of Access”.\r\n3. Scroll down to “Other options” and click on “Visual options”.\r\n4. Under “Play animations in Windows”, toggle the switch to “Off”.\r\n\r\n## One Last Thing...\r\n\r\nScript Kit AI course part 1 dropping this week... 💥","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-25T22:46:01Z"},{"name":"Chat Hello World","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1148","url":"","title":"Script Kit v1.51.2 - Welcoming our AI Overlords! 👑","command":"script-kit-v1512-welcoming-our-ai-overlords","content":"# Script Kit v1.51.2 - March 2023 Release\r\n\r\nDownload from https://www.scriptkit.com/\r\nJoin our Discord: https://discord.gg/qnUX4XqJQd\r\n❤️ Sponsor Script Kit development [https://github.com/sponsors/johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205) ❤️\r\n## Features\r\n\r\n### Build AI Conversations with the New `chat` Prompt\r\n\r\nYou can use the new `chat` prompt to create a chat interface for your scripts. The `chat` prompt returns a `messages` array that you can inspect to see the messages that were sent and received.\r\n\r\nIt supports keyboard navigation so you can press the up/down arrow keys to navigate through the messages and copy/paste them. You can also use `shortcuts` the same way you would with other prompts.\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/609d3f2dcbc49911698c3c2162310c0d/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n```\r\n\r\nThe `kit-examples` repo (bundled with new installs) has an example of using the latest ChatGPT model:\r\nhttps://github.com/johnlindquist/kit-examples/blob/main/scripts/chatgpt.js\r\n\r\n✨ Want more AI example scripts? Check out this post 👀: https://github.com/johnlindquist/kit/discussions/1143 ✨\r\n\r\n### The `eyeDropper` Color Picker\r\n\r\nYou can now use the `eyeDropper` function to pick a color from anywhere on your screen. It returns an object with `sRGBHex` to align with Chrome's Eyedropper tool.\r\n\r\n> Note: Unfortunately, windows restricts color picking to the foreground window. Still investigating a workaround.\r\n\r\n\r\n[Open testing-get-color in Script Kit](https://scriptkit.com/api/new?name=testing-get-color&url=https://gist.githubusercontent.com/johnlindquist/e10cc5dfc08fd5c9824bb3a7e5e50071/raw/76bb4b86f08e1f2a8b2f09d6e2ba47286dc42673/testing-get-color.ts\")\r\n\r\n```js\r\n// Name: Testing Get Color\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide()\r\nlet value = await eyeDropper()\r\nawait editor(value.sRGBHex)\r\n\r\n```\r\n\r\n### Node 18.12.1\r\n\r\nScript Kit now bundles Node 18.12.1. (Previous scripts ran on Node 16.17.2).\r\n\r\n### New Settings in `~/.kit/db/app.json` Features\r\n\r\n- \"mini\" boolean - Use Script Kit in a much smaller window\r\n- \"termFont\" string - Set the font for the terminal\r\n- \"cachePrompt\" boolean - If you experience issues with the prompt position caching logic, disable it so is _always_ show in the center of the screen\r\n- \"convertKeymap\" boolean - Script Kit auto converts to your selected OS keymap (dvorak, colemak, etc). There have been a few reports of this conflicting with Portuguese keyboards. If you experience issues, disable this setting.\r\n- \"searchDebounce\" boolean - by default, Script Kit will debounce when your choices list has over >1000 items. It's mostly a precaution and you can disable it if you'd like.\r\n\r\nThe defaults are as follows:\r\n```json\r\n{\r\n \"mini\": false,\r\n \"termFont\": \"monospace\",\r\n \"cachePrompt\": true,\r\n \"convertKeymap\": true,\r\n \"searchDebounce\": true\r\n}\r\n```\r\n\r\n## Fixes\r\n\r\n- Fixed a bug where `theme` was reset to the default when waking from sleep\r\n- Fixed the `Open in Script Kit` urls for Windows users\r\n- Fixed Calculator sizing from main menu\r\n- `await npm(\"package-name\")` will now mark a package as an \"external\" dependency in TypeScript so you can use import statements for packages that aren't installed yet and your script will still compile\r\n- Other minor bug fixes\r\n- Fixed installation issues when dealing with certs/proxies\r\n\r\n## Experimental\r\n\r\n### `toast()`\r\n\r\nYou can try out the experimental `toast(\"Copied\")` feature. I'm still working out the API, so there's no \"onClick\" or anything, but you should be able to pass the other toast properties (this uses `react-toastify`) explained [here](https://fkhadra.github.io/react-toastify/dispatch-toast-outside-of-react-component)","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-03T19:33:27Z"},{"name":"Chat Hello World","note":"This lib is updated fairly frequently to keep up with Google's changes: https://www.npmjs.com/package/googlethis","todo":"I'm sure this can be _vastly_ improved","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1143","url":"","title":"Script Kit AI Chat Pre-release","command":"script-kit-ai-chat-pre-release","content":"\r\nhttps://user-images.githubusercontent.com/36073/221332902-87c2d0a4-46b8-4eb1-a935-9a9fcae90e94.mp4\r\n\r\n## This is a Preview Build for Next Week's Release\r\n\r\nI'm releasing this to gather some feedback about the Chat component before I push the main release in the beginning of March. Please let me know below what you think of the new `chat` component. What do you love? What needs to change? What should be added? Please leave your ideas below! 🙏\r\n\r\nDownload here Pre-release here:\r\n\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.50.6\r\n\r\nJoin our Discord:\r\n\r\nhttps://discord.gg/qnUX4XqJQd\r\n\r\n## Chat Component Hello World\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/0f0f86d0dc84f7acb30dc726da39b470/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n\r\n## Chat Component with AI\r\n\r\n[Open openai-chat in Script Kit](https://scriptkit.com/api/new?name=openai-chat&url=https://gist.githubusercontent.com/johnlindquist/fc9f74aa387e371a59c973175201ead2/raw/e94fc92dec07dda63982bb54e756a35cfdb92532/openai-chat.ts\")\r\n\r\n```js\r\n// Name: OpenAI Chat\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { ConversationChain } = await import(\"langchain/chains\")\r\nlet { BufferMemory } = await import(\"langchain/memory\")\r\n\r\nlet llm = new OpenAI({\r\n openAIApiKey: await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n }),\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet memory = new BufferMemory()\r\nlet chain = new ConversationChain({\r\n llm,\r\n memory,\r\n})\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n await chain.call({ input })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n## Chat with a .txt File\r\n\r\n[Open doc-talk in Script Kit](https://scriptkit.com/api/new?name=doc-talk&url=https://gist.githubusercontent.com/johnlindquist/f1bfd6758815a1fb87d51a9c7893bd2e/raw/9d54e81aa9f269af0a22a44e6bfcb0f77b04f44d/doc-talk.ts\")\r\n\r\n```js\r\n/*\r\n# Doc Talk\r\n\r\nChat with a .txt file such as a book. In chat, ask questions like:\r\n- \"Who are the main characters?\"\r\n- \"Where are the key settings\"?\r\n- \"Summarize the story arc\"\r\n\r\n> Note: The chat's knowledge is limited to the loaded .txt file\r\n*/\r\n\r\n// Name: Doc Talk\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\nawait npm(\"hnswlib-node\")\r\n\r\nlet { OpenAI } = await import(\"langchain/llms\")\r\nlet { VectorDBQAChain } = await import(\"langchain/chains\")\r\nlet { HNSWLib } = await import(\"langchain/vectorstores\")\r\nlet { OpenAIEmbeddings } = await import(\r\n \"langchain/embeddings\"\r\n)\r\nlet { RecursiveCharacterTextSplitter } = await import(\r\n \"langchain/text_splitter\"\r\n)\r\n\r\nlet filePath = await path({\r\n hint: `Select a .txt file to talk to or Download Romeo and Juliet`,\r\n})\r\n\r\nif (filePath === \"__ROMEO__\") {\r\n let buffer = await download(\r\n `https://www.gutenberg.org/cache/epub/1513/pg1513.txt`\r\n )\r\n filePath = home(\"Downloads\", \"romeo-and-juliet.txt\")\r\n await writeFile(filePath, buffer)\r\n}\r\n\r\nwait(250).then(() => setLoading(true))\r\n\r\ndiv(\r\n md(`# Loading...\r\n\r\n~~~\r\nLoading in ${filePath}\r\n~~~\r\n`)\r\n)\r\n\r\nlet text = await readFile(filePath, \"utf-8\")\r\n\r\nconst model = new OpenAI({\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nconst textSplitter = new RecursiveCharacterTextSplitter({\r\n chunkSize: 1000,\r\n})\r\nconst docs = textSplitter.createDocuments([text])\r\n\r\nconst vectorStore = await HNSWLib.fromDocuments(\r\n docs,\r\n new OpenAIEmbeddings()\r\n)\r\n\r\nconst chain = VectorDBQAChain.fromLLM(model, vectorStore)\r\n\r\nsetLoading(false)\r\nlet messages = await chat({\r\n onSubmit: async query => {\r\n const res = await chain.call({\r\n input_documents: docs,\r\n query,\r\n })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Google AI\r\n\r\n\r\n[Open google-ai in Script Kit](https://scriptkit.com/api/new?name=google-ai&url=https://gist.githubusercontent.com/johnlindquist/3086fd1ddb12e16056cf116633f69547/raw/7ded0692a0909bbdd8b80eb7af69a5d2bf3d7352/google-ai.ts\")\r\n\r\n```js\r\n/*\r\n# Google AI Experiment\r\n\r\nThis is 100% experimental. I'm still learning the ins and outs of langchain.\r\nIf you have feedback on how to improve, PLEASE share 🙏\r\n\r\n\\- John Lindquist\r\n */\r\n\r\n// Name: Google AI\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\n// Note: This lib is updated fairly frequently to keep up with Google's changes: https://www.npmjs.com/package/googlethis\r\nawait npm(\"googlethis\")\r\nawait npm(\"@extractus/article-extractor\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { Tool } = await import(\"langchain/tools\")\r\nlet { initializeAgentExecutor } = await import(\r\n \"langchain/agents\"\r\n)\r\n\r\nclass GoogleThis extends Tool {\r\n name = \"search\"\r\n description =\r\n \"a search engine. useful for when you need to answer questions about current events. input should be a search query. Output should include the best result and associated URL.\"\r\n\r\n formatResults = response => {\r\n let data = response?.results\r\n ?.slice(0, 3)\r\n ?.map(r => {\r\n return `\r\ntitle: ${r.title}\r\ndescription: ${r.description}\r\nurl: ${r.url}\r\n`\r\n })\r\n .join(\"\\n\")\r\n\r\n if (response?.knowledge_panel?.title)\r\n data = `Best title: ${response?.knowledge_panel?.title}\r\n${data}`\r\n\r\n if (response?.knowledge_panel?.description)\r\n data = `Best description: ${response?.knowledge_panel?.description}\r\n${data}`\r\n\r\n return data\r\n }\r\n\r\n async call(input: string) {\r\n let google = await import(\"googlethis\")\r\n let response = await google.search(input)\r\n return this.formatResults(response)\r\n }\r\n}\r\n\r\nclass ReadURL extends Tool {\r\n name = \"read\"\r\n description = `a web scraper. Input is a url. Output is the contents of the page.`\r\n\r\n formatArticle = (url, article) => {\r\n let formatted = ``\r\n try {\r\n formatted = Object.entries(article)\r\n .filter(([key, value]) => key && value)\r\n .map(([key, value]) => {\r\n // In case the article contents are too long\r\n // TODO: Should probably wrap over to a \"Document\" paradigm here...\r\n if (typeof value === \"string\") {\r\n return [key, value?.slice(0, 1000) || \"\"]\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return [key, value.join(\",\")]\r\n }\r\n\r\n return [key, value]\r\n })\r\n .map(([key, value]) => `${key}: ${value}`)\r\n .join(\"\\n\")\r\n } catch (error) {\r\n formatted = `Couldn't read the contents of ${url}`\r\n }\r\n return formatted\r\n }\r\n\r\n async call(url: string) {\r\n let { extract } = await import(\r\n \"@extractus/article-extractor\"\r\n )\r\n let article = await extract(url)\r\n let formatted = this.formatArticle(url, article)\r\n\r\n return formatted\r\n }\r\n}\r\n\r\nlet tools = [new GoogleThis(), new ReadURL()]\r\n\r\nlet yankAnswer = async output => {\r\n return output?.generations\r\n ?.at(0)\r\n ?.at(0)\r\n ?.text.split(\"\\n\")\r\n ?.at(-1)\r\n .replace(\"Final Answer: \", \"\")\r\n}\r\n\r\nlet llm = new OpenAI({\r\n temperature: 0.7,\r\n streaming: true,\r\n callbackManager: {\r\n handleError: log,\r\n handleEnd: output => {\r\n let answer = yankAnswer(output)\r\n history = `${history}\r\nAI: ${answer}`\r\n },\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet executor = await initializeAgentExecutor(\r\n tools,\r\n llm,\r\n \"zero-shot-react-description\"\r\n)\r\n\r\n// TODO: I'm sure this can be _vastly_ improved\r\nlet history = `The AI should always include relevant URLs when possible.\r\nThe AI should use the given information and URLs to explain why it chose the answer.\r\nThe AI should avoid commas by formatting with newlines\r\n\r\n`\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n history = `${history}\r\nMe: ${input}`\r\n\r\n await executor.call({ input: history })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Read More about LangChain to Get Started with AI in Script Kit\r\n\r\nhttps://hwchase17.github.io/langchainjs/docs/overview\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-25T03:07:13Z"},{"alias":"hi","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1093","url":"","title":"Script Kit v1.48.0 - February 2023 Release","command":"script-kit-v1480-february-2023-release","content":"# Script Kit v1.48.0 - February 2023 Release\r\n\r\nDownload Script Kit v1.48.0 from: https://scriptkit.com\r\n\r\nWatch release video:\r\nhttps://www.youtube.com/watch?v=FQg8AL539_M\r\n\r\n## Drag and Drop Widgets\r\n\r\n![CleanShot 2023-02-02 at 14 31 33](https://user-images.githubusercontent.com/36073/216454341-e1aa117f-c238-4a6b-83af-075c463773ab.gif)\r\n\r\n\r\nWidgets now support `onDrop` and `onMouseDown` handlers. `onMouseDown` can be used in conjunction with `startDrag` to create drag and drop widgets as seen below.\r\n\r\nYou can put any sort of logic in the `onDrop` such as processing files using `ffmpeg`, uploading files to a server, etc, etc.\r\n\r\nThe widget `setState` can be used to show files/progress/etc in effect creating tiny application crafted _just for you_ 🤩\r\n\r\n```js\r\nlet files = []\r\n\r\nlet w = await widget(\r\n `
\r\n
Drop Files
\r\n{{file}}\r\n
\r\n`,\r\n {\r\n containerClass: `p-4 h-screen w-screen overflow-auto`,\r\n width: 300,\r\n height: 300,\r\n draggable: false,\r\n state: {\r\n files,\r\n },\r\n }\r\n)\r\n\r\nw.onDrop(event => {\r\n if (event?.dataset?.files) {\r\n files.push(...event.dataset.files)\r\n w.setState({\r\n files,\r\n })\r\n }\r\n})\r\n\r\nw.onMouseDown(event => {\r\n if (event.dataset.file) {\r\n startDrag(event.dataset.file)\r\n }\r\n})\r\n```\r\n\r\n## `// Alias` Metadata\r\n\r\nHave a script you run often? Use `// Alias` to jump that script to the top of the list when that alias is hit:\r\n\r\n```js\r\n// Alias: hi\r\nsay(\"hi\")\r\n```\r\n\r\n## Fixes for Windows/Linux Installs\r\n\r\nAfter a huge influx of users since the beginning of the year on a variety of different systems and setups, we've been able to test and fix most of the issues that have popped up around various setups with a huge focus on the installation process for Script Kit on Windows and Linux.\r\n\r\nIf you're finding that you're having issues installing Script Kit, please see this discussion: https://github.com/johnlindquist/kit/discussions/1052\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-02T21:32:51Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/970","url":"","title":"Script Kit v1.39.17 December Release","command":"script-kit-v13917-december-release","content":"# Script Kit v1.39.17 December Release\r\n\r\n\r\n\r\n\r\nDownload from https://www.scriptkit.com/\r\n\r\n## Kit.app Re-Design\r\n\r\nScript Kit now has a new look. The theming was completely re-written to help build out customs themes, so we took the opportunity to clean up the main theme.\r\n\r\nYou may also notice subtle differences in app shadows, transparency, etc which are thanks to the latest Electron updates supporting \"Panel\" windows (which we were accomplishing through third-party, less-than-ideal ways before). Speaking of updates...\r\n\r\n## Updated to the Latest Versions\r\n\r\nScript Kit is now on the latest Electron 22, node 16.17.1, and many of the internal dependencies how been updated as well. If you're a developer, you know how big of an undertaking this can be (and how refreshing it is when you're done).\r\n\r\nElectron 22 is an exciting release from a dev standpoint and we can't wait to dig into using some of the new features.\r\n\r\n## Alternate Keymap Support\r\n\r\nScript Kit shortcuts will now support your custom/international keymaps. If will also detect if a keymap has changed and remap based on the new configuration\r\n\r\n> Note: If you have a shortcut that accounted for the keymap being \"wrong\", you'll need to update it to the correct version\r\n\r\n## Run a Script From Other Apps with ~/.kit/run.txt\r\n\r\nIf you want to launch Script Kit from the terminal or another app, write the command and arguments to the `~/.kit/run.txt` file. \r\nScript Kit watches for changes and will run the command you write to the file.\r\n\r\nThe following will run the script `browse-scriptkit.js` in your ~/.kenv/scripts dir:\r\n\r\nMac:\r\n```bash\r\necho browse-scriptkit > ~/.kit/run.txt\r\n```\r\n\r\nWindows:\r\n```cmd\r\necho browse-scriptkit > %HOMEPATH%\\.kit\\run.txt\r\n```\r\n\r\n> Note: You can still use ~/.kit/kar, but I wanted to offer an alternative to our Windows friends\r\n\r\n## Watchers Menu\r\n\r\n> 🚨 Note: It's now required to manually start the snippet/clipboard watcher from the menubar icon->Watchers menu.\r\n\r\n Some users reported difficulty/freezing with the keyboard monitoring for snippets due to various other app conflicts, hardware conflicts, etc, so we decided to allow more control over starting/stopping/waking the watcher in case something happens. We worked hard in this release to address these issues, but decided it's still best to give control to the user.\r\n\r\n## Windows setSelectedText() Fixed\r\n\r\nWindows will now properly hide the prompt to be able to paste text to the app behind it\r\n\r\n## Soon... Scripts on GitHub Actions\r\n\r\nMade significant progress towards using Script Kit on GitHub Actions. Need some more time to test and handle edge cases, but we're close!!!","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-12-08T02:29:43Z"},{"enter":"Rotate Images","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/949","url":"","title":"Script Kit v1.36.0 November 2022 Release","command":"script-kit-v1360-november-2022-release","content":"# Script Kit v1.36.0 November 2022 Release\r\n\r\nDownload from [https://www.scriptkit.com/](https://www.scriptkit.com/)\r\n\r\n\r\n
\r\n \r\n
\r\n\r\n\r\n## Script Markdown\r\n\r\nPlace a multiline comment at the top of your script to display Markdown to the user when previewing the script. For example:\r\n\r\n```js\r\n/*\r\n# Rotate Images\r\n- Get the selected image from Finder\r\n- Creates 3 new versions rotated at 90, 180, and 270\r\n*/\r\n```\r\n\r\n## Script `Enter` Metadata\r\n\r\nAdding `Enter` metadata will now change the text displayed by the \"Run\" button in the script preview. For example:\r\n\r\n```js\r\n// Enter: Rotate Images\r\n```\r\n\r\n## Themes (Pro)\r\n\r\nFrom the \"Kit\" tab, you can now select a theme for the Script Kit UI. There are a variety of themes to choose from and in upcoming releases, you'll be able to create your personalized themes that you can share.\r\n\r\n## Log Window (Pro)\r\n\r\nRun a script with `alt+enter` to open the log window. This window will display the output of your script from and commands you run or any console logs.\r\n\r\n## Debugger (Pro)\r\n\r\nRun a script with `ctrl+enter` to open the debugger. The debugger will automatically pause on any `debugger` statements and allow you to step through your script and inspect/modify variables.\r\n\r\n## Account\r\n\r\nYou can now sign in to GitHub to unlock more features. The first feature is `createGist` (which is used behind the scenes in sharing scripts already) which is now exposed to users:\r\n\r\n```js\r\nlet {url} = await createGist(\"My content\")\r\n```\r\n\r\nIn the future, this account will be used for:\r\n- syncing your scripts with a GitHub repo\r\n- connecting to GitHub repos to run GitHub Actions\r\n- displaying stats about your scripts\r\n- pro plan/team plans\r\n- and much more...\r\n\r\n\r\n## Widgets Dynamic Lists\r\n\r\nWidgets can now use dynamic lists and get data from an item selected. Please check out this example and watch the youtube video for more details.\r\n\r\nhttps://github.com/johnlindquist/kit/discussions/948\r\n\r\n## Snippet Keyboard Layouts\r\n\r\nSome users reported that Snippets were not working on non-standard keyboard layouts. The snippet engine has been updated to detect your current system keyboard layout and adjust accordingly.\r\n\r\n## Focus Window\r\n> Requires \"Security & Privacy\" > \"Accessibility\" > \"Screen Recording\" permission to be enabled to work so it can get the window title names.\r\n\r\nHit colon (:) from the main menu to open the focus window script. This lists all of the windows open and allows you to select which window to bring into focus.\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-11-18T17:39:00Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/925","url":"","title":"October 2022 Release (v1.32.2) - Windows Preview 🤩, Debugger 🐞, and Repairs 🛠️","command":"october-2022-release-v1322-windows-preview-debugger-and-repairs","content":"# Script Kit 1.32.2 Released 🎉\r\n\r\nDownload here: https://scriptkit.com\r\n\r\n## Windows Preview 🤩\r\n\r\nWe are excited to announce the first Windows preview of Script Kit! This build is very close to feature-parity with the OSX version, so you can expect the vast majority of scripts you run on Mac to also run on Windows.\r\n\r\n> Note: We haven't purchased the Windows code signing certificate yet, so Windows will warn you that it can't verify Script Kit when you begin the install process. Also, this causes that Windows updates will need to be installed manually. We plan on setting up the Windows code-signing certificate by the end of the year to fix these issues then we'll remove the \"Preview\" label 😊\r\n\r\nSnippets, Watchers, Schedule, etc, etc, etc all work. If an api is not supported on Windows (mostly the functions that get information about the desktop) then it will display a \"Not supported on Windows\" message.\r\n\r\nThe \"Browse\" (`/` from the main menu) and \"File Search\" (`.` from the main menu) both required a ton of work, but they're working as well. We'll get the App Launcher sorted out in the next release.\r\n\r\n## Debugger\r\n\r\nYou can now debug your scripts by simply pressing `cmd+enter` from the main menu (`ctrl+enter` on Windows). This will open a built-in inspector that will allow you to step through your script, set breakpoints, and inspect variables.\r\n\r\nUse the `debugger` keyword to set a breakpoint in your script. The inspector will pause execution when it hits the breakpoint and you can mess around with the variables and step through the script to your heart's content. You can even invoke functions such as `setDescription()` and treat the inspector like a REPL.\r\n\r\n## Repair\r\n\r\nThe menubar now includes a `Debug` menu. From `Debug->Force Repair Kit SDK` you can force the Kit SDK to be reinstalled. This is useful if you're having issues with the Kit SDK due to npm acting up or if an update failed.\r\n\r\n## `await cutText()`\r\n\r\nThe `cutText()` function will cut the latest typed word and bring it into your script.\r\n\r\n```js\r\nlet word = await cutText()\r\n\r\n// Send word to an API, wrap the word in markdown, etc, etc\r\n```\r\n\r\n## Other Fixes\r\n\r\n* The TypeScript esbuild compiling should be faster and more stable\r\n* New install splash now shows the npm progress status\r\n* App Launcher fixes (`;` from the main menu)\r\n* Google Fixes (`~` from the main menu)","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-10-27T16:08:15Z"},{"name":"Example Postfix Snippet","snippet":"*html,,","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/901","url":"","title":"September 2022 Release (version 1.30.8) - Postfix Snippets, UI Shortcuts, VS Code Extension, and Much, much more!","command":"september-2022-release-version-1308-postfix-snippets-ui-shortcuts-vs-code-extension-and-much-much-more","content":"# Script Kit 1.30.8 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\n\r\nThis is our first release after a couple of busy summer months, and it's slam-packed full of new features and UX!\r\n\r\n## Postfix Snippets\r\n\r\nPostfix snippets are snippets that are triggered by typing a postfix after a \"variable\" of text. The variable is represented by a `*` at the beginning of snippet. In the `*html,,` example below, typing `divhtml,,` would treat `div` as the variable and render out `
Hello world
`.\r\n\r\n### Postfix Snippet Hello World\r\n\r\n```js\r\n// Name: Example Postfix Snippet\r\n// Snippet: *html,,\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"I'm the '*' content\")\r\n\r\nsetSelectedText(`<${value}>Hello world${value}>`)\r\n```\r\n\r\n### Postfix Snippet Query API\r\n\r\nThe snippet can also take the content of the `*` and post it to an API for more complex scripts.\r\n\r\nIn this example, the content is used to query google and create a markdown link from the word you typed.\r\n\r\n```js\r\n// Name: Markdown Link from Google Snippet\r\n// Snippet: *,.\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet google = await npm(\"googlethis\")\r\n\r\nconst options = {\r\n page: 0, \r\n safe: false,\r\n additional_params: { \r\n hl: 'en' \r\n }\r\n}\r\n\r\nlet value = await arg(\"I'm the asterisk content\")\r\nlet response = await google.search(value, options);\r\n\r\nlet url = response.results[0].url\r\n\r\nsetSelectedText(`[${value}](${url})`)\r\n```\r\n\r\n\r\n## `template()`\r\n\r\nThe `template` prompt will present the editor populated by your template. You can then tab through each variable in your template and edit it. \r\n\r\nTemplates pair really, _really_ nicely with Snippets!\r\n\r\n### Template Hello World\r\n\r\n```js\r\nlet text = await template(`Hello $1!`)\r\n```\r\n\r\n### Standard Usage\r\n\r\n```js\r\nlet text = await template(`\r\nDear \\${1:name},\r\n\r\nPlease meet me at \\${2:address}\r\n\r\n Sincerely, John`)\r\n```\r\n\r\n\r\n## `await docs()`\r\n\r\n`docs()` takes the file path of a markdown file and displays it as a list of choices.\r\n\r\nEach h2 is displayed as a choice while the content of the h2 is displayed in the preview.\r\n\r\nSelected the choice will return current h2.\r\n\r\n### Submitting a `docs()` value\r\n\r\nIf you'd rather submit a value instead of the h2, then use an HTML comment to specify the value under the h2's content:\r\n\r\n```md\r\n## I'm the Choice Header\r\n\r\n```\r\n\r\n### `docs()` Example\r\n\r\n```js\r\n// Name: Example Docs\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await download(`https://raw.githubusercontent.com/BuilderIO/qwik/main/README.md`)\r\n\r\nlet filePath = tmpPath(\"README.md\")\r\nawait writeFile(filePath, buffer)\r\n\r\n\r\nlet selectedDoc = await docs(filePath)\r\n\r\ndev({selectedDoc})\r\n```\r\n\r\n## UI Shortcuts in the \"Action Bar\"\r\n\r\nThe September 2022 release adds a new \"Action Bar\" at the bottom of the UI which supports custom shortcuts.\r\n\r\nA shortcut has a `name`, `key`, `onPress` and `bar` property. When you press the shortcut, it will trigger the `onPress` function. You can also click on the shortcut to trigger the `onPress` function.\r\n\r\n```js\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n```\r\n\r\n\r\nAdd each shortcuts to a `shortcuts` array which is passed to the prompt config (most commonly, the first argument of `arg()`).\r\n\r\n### UI Shortcuts Example\r\n\r\n```js\r\n// Name: Shortcuts Example\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n\r\nlet reloadChoices = {\r\n name: \"Reload\",\r\n key: \"cmd+l\",\r\n bar: \"right\",\r\n onPress: async () => { \r\n setChoices([\"one\",\"two\",\"three\"])\r\n }\r\n} \r\n\r\nlet submitInputNotChoice = {\r\n name: \"Submit Input\",\r\n key: \"cmd+i\",\r\n bar: \"right\",\r\n onPress: async (input) => { \r\n submit(input)\r\n }\r\n} \r\n\r\nlet runAppLauncher = {\r\n name: \"Launch App\",\r\n key: \"cmd+a\",\r\n bar: \"left\",\r\n onPress: async () => { \r\n await run(kitPath(\"main\", \"app-launcher.js\"))\r\n }\r\n}\r\n\r\nlet result = await arg({\r\n placeholder: \"Shortcut demo\",\r\n shortcuts: [ \r\n clearInput,\r\n reloadChoices,\r\n submitInputNotChoice,\r\n runAppLauncher\r\n ]\r\n}, [\"apple\", \"banana\", \"cherry\"])\r\n\r\nawait div(md(`## ${result}`))\r\n```\r\n\r\n## Recent Script Moved to Top\r\n\r\nThe most recently run script is now moved to the top of the list so that the next time you open the main prompt, you can quickly run it again.\r\n\r\n\r\n## VS Code Extension\r\n\r\nWe now have a VS Code extension which allows you to run scripts directly from VS Code:\r\n\r\nhttps://marketplace.visualstudio.com/items?itemName=johnlindquist.kit-extension\r\n\r\nMore features are coming soon!\r\n\r\n## Menubar Updates\r\n\r\nThe menubar menu now has a \"Dev Tools\" menu which allows you to perform some common commands that might not be obvious from the main UI.\r\n\r\nThe menu also lists all of the running processes so that you can easily terminate them without having to hunt around from process ids.\r\n\r\n## cmd+tab to Widgets and `dev`\r\n\r\nWhen the main prompt is open, a widget is open, or a `dev` window is open, the Script Kit icon will be added to the doc to allow you to cmd+tab back to widgets/editor/dev/etc. Since Script Kit is a combination of a temporary prompt (like Alfred) but also can host long-running widgets, we had to work through various scenarios of when cmd+tab can be available. Hopefully we've landed on a solution that works for everyone.\r\n\r\n## Main Menu `API` and `Guide`\r\n\r\nThe Main Menu of Script Kit now hosts `API` and `Guide` tabs which allow you to easily copy code snippets or create new scripts from the examples. They're also both easy to update, so you can expect more samples and explanations to play with in the future!\r\n\r\n## `await emoji()`\r\n\r\nA brand new emoji picker\r\n\r\n```js\r\nlet e = await emoji()\r\nsetSelectedText(e.emoji)\r\n```\r\n\r\n## `await fields()`\r\n\r\n```js\r\nlet [first, last] = await fields([\"First name\", \"Last name\"])\r\n\r\ndev({\r\n first,\r\n last\r\n})\r\n```\r\n\r\n## `beep()`\r\n\r\nAnd the most exciting announcement of all, `beep()` is now available!\r\n\r\n`beep()` plays a beep sound.\r\n\r\n### `beep()` Example\r\n\r\n```js\r\nbeep()\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-09-29T08:49:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/787","url":"","title":"June 2022 Release (version 1.19.3) - Menu, Native Keyboard, ignoreBlur","command":"june-2022-release-version-1193-menu-native-keyboard-ignoreblur","content":"# Script Kit 1.19.3 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\nDirect downloads:\r\n* Intel: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3.dmg\r\n* m1: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3-arm64.dmg\r\n\r\n## Features\r\n\r\n* Moved all notifications to menu colored “dots” (red, orange, green)\r\n * In the future, the user will be able to set these notifications as well\r\n* Completely re-vamped menu for common options\r\n* Implemented an “auth helpers” when user does actions that require “accessibility permissions” (Snippets, clipboard history, setSelectedText, etc)\r\n* `ignoreBlur` allows window to go behind other windows and stay open\r\n* `node` is now stored in `~/.knode` (instead of ~/.kit/node) to allow npx to work in the terminal\r\n* `node` version set to v16.14.2. Version is now synced with Kit.app which resolves conflicts with native packages in scripts\r\n* Keyboard actions (copy/paste/type) have moved from applescript to native code. Snippets, setSelectedText, etc should now feel as “instant” as possible.\r\n* You can now `await hide()` for when you need to make sure the prompt is hidden before continuing the script. This was necessary since the new keyboard actions were so fast.\r\n* Moved the script sharing auth flow to a widget\r\n* Internal: Can now set the state of Kit.app from a script to help with debugging\r\n\r\n\r\n## Fixes\r\n\r\n* App launcher failed to parse malformed App plist icons\r\n* Editor whitespace collapsing on HiDPI screens\r\n* Touchbar key while prompt open would cause crash\r\n* Bin files sometimes didn’t regenerate properly when re-launching the app\r\n* Updater issues\r\n* Background UI not updating when user manually terminates process\r\n* Performance: Moved the file watcher to a spawned process that sends events to the App\r\n\r\n## More to come...\r\n\r\nFinally moved into the new house and settled in. Personal life was too hectic to stream much or do release notes on past couple releases. Expect more streaming, sharing scripts, promotion, and news from me. Cheers! 🥂\r\n\r\n_Here's a preview of the new menu and an example of a dot notification_\r\n\r\n\r\n![The new dot notifications and re-vamped menu](https://cdn.discordapp.com/attachments/963905444823318578/988844882665803926/unknown.png)\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-06-21T16:44:14Z"},{"name":"Widget Hello World","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/745","url":"","title":"March 2022 Release (version 1.7.3) - Widgets! Built-in Terminal, Menu, and Built-in Editor Types","command":"march-2022-release-version-173-widgets-built-in-terminal-menu-and-built-in-editor-types","content":"# March 2022 Release (version 1.7.3)\r\n\r\nScript Kit should auto-update or you can grab the downloads here: https://www.scriptkit.com/\r\n\r\n## Widgets - `await widget()`\r\n\r\nA widget is a detached UI window that can control and listen to a script.\r\n\r\n\r\n\r\n[Open widget-hello-world in Script Kit](https://scriptkit.com/api/new?name=widget-hello-world&url=https://gist.githubusercontent.com/johnlindquist/ca174899643e86f416d301d9599bb4e8/raw/55d334c6dc412c0346a750348d8c0ffa2b8650ba/widget-hello-world.ts\")\r\n\r\n```js\r\n// Name: Widget Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet message = await arg(\"Hello what?\")\r\nawait widget(`
Hello ${message}
`)\r\n\r\n```\r\n\r\n### Widget Events\r\n\r\n\r\n[Open widget-events in Script Kit](https://scriptkit.com/api/new?name=widget-events&url=https://gist.githubusercontent.com/johnlindquist/1ce2972fdeed0773450f4dba3f3f2c00/raw/6834ccde194ea471f403df9366a7ac283cb853bb/widget-events.ts\")\r\n\r\n```js\r\n// Name: Widget Events\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = \"\"\r\nlet count = 0\r\n\r\nlet w = await widget(`\r\n
\r\n`)\r\n})\r\n\r\nw.onInput((event) => {\r\n text = event.value\r\n})\r\n\r\nw.onMoved(({ x, y}) => {\r\n // e.g., save position\r\n})\r\n\r\nw.onResized(({ width, height }) => {\r\n // e.g., save size\r\n})\r\n```\r\n\r\n## Closing a Widget\r\nThere are 3 ways to close a widget:\r\n1. Hit \"escape\" with the widget focused\r\n2. End the process of the widget. Hit cmd+p with the main menu focused to see the running processes or `exit()` anywhere in the script.\r\n3. Use a `ttl` (time to live) in the options when creating a widget\r\n\r\n## \"Always on Top\" and Locking the Widget\r\n\r\n[Open widget-always-on-top in Script Kit](https://scriptkit.com/api/new?name=widget-always-on-top&url=https://gist.githubusercontent.com/johnlindquist/bfd8ec67d9632867b0faf4e808381948/raw/90f766f21af8c88760409215e569baef9d8f0238/widget-always-on-top.ts\")\r\n\r\n```js\r\n// Name: Widget Always on Top\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait widget(`
🇺🇦
`, {\r\n alwaysOnTop: true,\r\n transparent: true\r\n})\r\n\r\n```\r\n\r\nWith a widget focused, press cmd+l to \"Lock\" the widget. This will disable any possible mouse interactions (including moving, resizing, etc) and allow you to click through the widget to any windows underneath.\r\n\r\nTo \"Unlock\":\r\n1. three-fingered swipe up on OSX\r\n2. focus the widget\r\n3. hit cmd+l\r\n\r\nYou can now hit move, escape, etc the widget.\r\n\r\n## Built-in Terminal - `await term()`\r\n\r\n`term` is Script Kit's built-in terminal.\r\n\r\n### From the Main Menu\r\n\r\nType > into the main menu to open `term`\r\n\r\n### From a Script\r\n\r\nUse the `await term()` API to switch to the terminal.\r\n\r\n[Open term-hello-world in Script Kit](https://scriptkit.com/api/new?name=term-hello-world&url=https://gist.githubusercontent.com/johnlindquist/10420bab68da357b572c1e703c2c5a43/raw/a287e443f1e57d4541f50feba28b86e1702ff515/term-hello-world.ts\")\r\n\r\n```js\r\n// Name: Term Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait term(`echo 'Hello World!'`)\r\n```\r\n\r\n> Note: If you want spawn a new Mac terminal, use `await terminal()`\r\n\r\n### Pass Terminal Output to Script\r\n\r\nIf you end the terminal with cmd+enter, the script will continue and you can grab the latest text output from the terminal.\r\n\r\n> 🐞: ctrl+any key will also end the terminal. This is a bug (it was only meant to be ctrl+c) which I'll fix soon. I'm also open to ideas for other shortcuts to \"end\" a terminal that aren't taken by vim/emacs/etc, because I know I'll be missing some.\r\n> 🐞: `term` doesn't grab keyboard focus when opening. I'll get that fixed ASAP!\r\n\r\n[Open term-returns in Script Kit](https://scriptkit.com/api/new?name=term-returns&url=https://gist.githubusercontent.com/johnlindquist/935445caef26d1c13f195533569cd0cc/raw/ca921b09480a3d7b5bf63e7a777011199e642fb9/term-returns.ts\")\r\n\r\n```js\r\n// Name: Term Returns\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = await term(`ls ~/.kit`)\r\nawait editor(text)\r\n```\r\n\r\n## Menubar - `menu()`\r\n\r\n`menu` allows you to customize the menubar for Script Kit.\r\n\r\n\r\n\r\n[Open menu-hello-world in Script Kit](https://scriptkit.com/api/new?name=menu-hello-world&url=https://gist.githubusercontent.com/johnlindquist/6aeb6a3f916bfc40e9acd6b9d4388b34/raw/21d8c70da05f08e454d81cb0ccebbbbc82b42f7d/menu-hello-world.ts\")\r\n\r\n```js\r\n// Name: Menu Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nmenu(`Hello 🌎`)\r\n```\r\n\r\n### Menu with Scripts\r\n\r\nThe second arg of menu can be an array of scripts you wish to present in a drop-down menu. This way, on left-click, you'll get a list of scripts to pick from from the menubar rather than opening the main Script Kit UI.\r\n\r\n[Open menu-with-scripts in Script Kit](https://scriptkit.com/api/new?name=menu-with-scripts&url=https://gist.githubusercontent.com/johnlindquist/0e07e0a8bd4926be6d843ce49fbb4474/raw/4a111ceeae7725525fdcf8bf546105698a4ac4c9/menu-with-scripts.ts\")\r\n\r\n```js\r\n// Name: Menu with Scripts\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n// An empty string means \"Use default Script Kit icon\"\r\nmenu(``, [\r\n \"app-launcher\"\r\n])\r\n```\r\n\r\n## Built-in Editor Types\r\n\r\nScript Kit's built-in editor now loads all of Script Kit's types! This was a huge undertaking that everyone just expects to work. You all know how that feels 😇\r\n\r\n> 🐞: Please let me know if you see any missing. I noticed that I missed the types for `terminal` and `iterm` when putting this post together 🤦♂️.\r\n\r\n## March Plans\r\n\r\nI'm dedicating March to DOCUMENTATION!!! (and bug-fixes)... I have _a lot_ of script requests to follow-up on and work around the newsletter and other non-app stuff. I'm also moving this month, so y'all know how stressful that can be. So expect the April build to be extremely light feature-wise, but I will be set up in the new house ready to much more live-streaming and communication. Can't wait to share more! 🙂\r\n\r\n## Questions?\r\n\r\nI'm happy to help with any questions you may have!","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-03-01T19:45:06Z"},{"name":"Menubar Demo","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/705","url":"","title":"February 2021 (version 1.6.9) - Built-in Editor, New Main Menu Features, `await path()`, Event handlers, Beta pro features, Terminate Process","command":"february-2021-version-169-built-in-editor-new-main-menu-features-await-path-event-handlers-beta-pro-features-terminate-process","content":"Been a _busy_ month of major Script Kit features!\r\n\r\n## Built-in Editor\r\n\r\nScript Kit's number one goal is to make writing time-saving scripts easier. So now Script KIt comes with a pre-configured editor, complete with autocompletion and error checking:\r\n\r\nhttps://user-images.githubusercontent.com/36073/152349718-9f9af13f-4cbb-4444-810a-2f1281938106.mp4\r\n\r\nIf you're already using vs code, you can switch to the \"Kit\" editor in the `Kit` tab -> `Change Editor`.\r\n\r\n## Main Menu Shortcuts and `/`, `~`, and `>`\r\n\r\nThe Script Kit main menu will continue to grow in features. \r\n\r\n### Shortcuts\r\n\r\n* `cmd+f` - Does a `grep` search through all of your scripts\r\n* `cmd+p` - Launches a `Processes` menu for currently running scripts\r\n\r\n### Path Mode\r\nType the following characters to change the mode of the main menu:\r\n* ~ Switches to a path selector mode in your home directory\r\n* / Switches to a path selector mode in your root directory\r\n\r\nNavigate with right/left or tab/shift+tab then select with return. Here's an example of typing `~`\r\n\r\nhttps://user-images.githubusercontent.com/36073/152386816-8be054d6-047c-416f-ae2b-dce1723d222c.mp4\r\n\r\n### Command Mode\r\n\r\n* > Switches to a command mode to execute a command\r\n\r\n\r\nhttps://user-images.githubusercontent.com/36073/152387376-b2e8b71a-4980-4d23-8c98-4f56f3ce1fdd.mp4\r\n\r\n\r\n### Future Work\r\nIn the March release, planning on these:\r\n\r\n* , List system preferences\r\n* . App launcher\r\n* ; MEGA MENU WITH EVERYTHING 🤭\r\n\r\n## `await path()`\r\n\r\nYou can now prompt to select a path. This UI works exactly like \"path mode\" above.\r\n\r\n```js\r\nlet selectedPath = await path()\r\ncd(selectedPath)\r\n\r\nawait exec(`git pull`) // this will now operate based on the selectedPath\r\n```\r\n\r\n## Event Handlers\r\n\r\nWhen building the `path` prompt, I realized it just wasn't possible to do in a script. So I put in the effort to expose the event handlers from the app into the prompt. So even though `path` behaves very differently, it's still an `arg` with customized handlers. You can override many of the handlers yourself for customized prompts:\r\n\r\nFor example, you can override the default behavior of `Escape` terminating your current script:\r\n\r\n```js\r\n// Submit the current input when you hit escape\r\nawait arg({\r\n onEscape: (input)=> {\r\n submit(input)\r\n }\r\n})\r\n```\r\n\r\nOverriding handlers is definitely considered \"advanced\", so I'm happy to answer any questions!\r\n\r\nHere's a list of all the new `arg` config properties:\r\n```js\r\nexport interface ChannelHandler {\r\n (input: string, state: AppState): void | Promise\r\n}\r\n\r\nexport interface PromptConfig\r\n onNoChoices?: ChannelHandler\r\n onChoices?: ChannelHandler\r\n onEscape?: ChannelHandler\r\n onAbandon?: ChannelHandler\r\n onBack?: ChannelHandler\r\n onForward?: ChannelHandler\r\n onUp?: ChannelHandler\r\n onDown?: ChannelHandler\r\n onLeft?: ChannelHandler\r\n onRight?: ChannelHandler\r\n onTab?: ChannelHandler\r\n onInput?: ChannelHandler\r\n onBlur?: ChannelHandler\r\n onChoiceFocus?: ChannelHandler\r\n\r\n debounceInput?: number\r\n debounceChoiceFocus?: number\r\n\r\n onInputSubmit?: {\r\n [key: string]: any\r\n }\r\n onShortcutSubmit?: {\r\n [key: string]: any\r\n }\r\n}\r\n```\r\n\r\n## onInputSubmit, onShortcutSubmit\r\n\r\nIf you want to create \"shortcuts\" to submit specific values, can use the new `onInputSubmit` and `onShortcutSubmit`. These allow you to bind text or shortcuts to submit values. This is exactly how the main menu works:\r\n\r\n![CleanShot 2022-02-03 at 09 47 42](https://user-images.githubusercontent.com/36073/152388672-db242f4e-20e2-4645-a326-a8bbc960f63d.png)\r\n\r\n\r\n## Beta Pro Features: Menubar\r\n\r\nYou can now customize the text of the Script Kit menubar icon to say anything with the `pro.beta.menubar` method. In the future, you'll be able to build out an entire menu, but I thought I'd sneak this feature in for fun in this build:\r\n\r\n[Open menubar-demo in Script Kit](https://scriptkit.com/api/new?name=menubar-demo&url=https://gist.githubusercontent.com/johnlindquist/ef01308eb63715970f26ee1378473194/raw/9ad4219968d9ec1a9747811a71c678ad8e241ec0/menubar-demo.ts\")\r\n\r\n```js\r\n// Name: Menubar Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"Set the menubar to:\")\r\npro.beta.menubar(value)\r\n\r\n\r\n```\r\n\r\nhttps://user-images.githubusercontent.com/36073/152394319-d9e071fe-edcd-4cf2-be3e-60d5ba7b01cd.mp4\r\n\r\n\r\n## Terminate Processes\r\n\r\nIf you need to end a script that's running in the background, stuck on an exec command, or whatever reason, open the main menu with the cmd+; shortcut, then press this button (or hit cmd+p. This will open a \"terminate processes\" window where you can end your scripts:\r\n\r\n![CleanShot 2022-02-03 at 10 20 45@2x](https://user-images.githubusercontent.com/36073/152394881-cf612921-1f00-4458-861a-3538053377dd.png)","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-01-30T00:36:03Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/669","url":"","title":"Script Kit for Linux - Developer Preview","command":"script-kit-for-linux-developer-preview","content":"# Script Kit for Linux - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. Building from the \"Mac\" source\r\n\r\nCurrently, the Linux build builds from the exact same branch as the Mac build. While this works fine, for now, we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out details next year.\r\n\r\n2. The Linux build is missing all the OS-specific tools\r\n\r\nLinux currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n3. I've Only Tested on Ubuntu through a Parallels vm\r\n\r\nObviously will need some more real-world testing.\r\n\r\n## Where to Download\r\n\r\nDownload the AppImage here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-12-24T08:15:12Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/655","url":"","title":"🥳 Script Kit Launch Day 🎉","command":"script-kit-launch-day","content":"# Script Kit is Officially Released! 🎉\r\n\r\nDownload now from https://scriptkit.com\r\n\r\n## Free Script Kit Course on [egghead.io](https://egghead.io)\r\n\r\nTo help you get started, I put together a short course as a feature tour:\r\n\r\nhttps://egghead.io/courses/script-kit-showcase-for-optimizing-your-everyday-workflows-e20ceab4\r\n\r\nIf you've been using Script Kit for a while on the beta, you know it can do much, much more than what's shown in the lessons, but everyone has to start somewhere. Speaking of the beta...\r\n\r\n## Beta Channel Discontinued\r\n\r\nIf you installed the beta, please download from https://scriptkit.com, quit Kit.app, and replace with the new version. This will put you on the “Main” channel. Updates will be ~monthly. The beta channel is discontinued ❗️\r\n\r\nAlso, thank you so, so much for all your feedback and patience with updates over the past year. You’ve really helped make Script Kit what it is today and I’m forever grateful 🙏\r\n\r\n## Windows Developer Preview\r\n\r\nThe details for the Windows build are found here: https://github.com/johnlindquist/kit/discussions/654\r\n\r\n## Plans for 2022\r\n\r\n1. Make the dev setup more contribution-friendly. I would love to accept PRs later next year.\r\n2. Get the Windows build to parity with Mac.\r\n3. Lots of lessons and scripts. I can finally spend more time sharing scripts than working on the app 😎\r\n4. Research into Rust, runtimes, and utilities that can provide any benefit to making our scripts better.\r\n5. Focus on \"export to serverless function\", \"export as github action\", and other ways to maximize the work you put into your scripts.\r\n5. Script Kit Pro. A paid version with additional features not found in the base version. Not ready to talk about it, but it's exciting!\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-12-17T18:33:08Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/654","url":"","title":"Script Kit for Windows - Developer Preview","command":"script-kit-for-windows-developer-preview","content":"# Script Kit for Windows - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. I haven't bought a certificate to add to the build:\r\n- You'll see many \"untrusted\" warnings when downloading/installing\r\n- Auto-updating will not work\r\n\r\n2. I haven't decided if the Windows repo will be a fork, branch, or main\r\n\r\nCurrently, the Windows build builds from the exact same branch as the Mac build. While this works fine, for now, I'm pretty sure we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out the details next year.\r\n\r\n3. The Windows build is missing all the OS-specific tools\r\n\r\nWindows currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n4. I've Only Tested It on Two Laptops\r\n\r\nThe Mac version has been used/tested by many, many people. I have two Windows laptops at home to test it on. It works well, but I don't know how much your mileage will vary.\r\n\r\n## Where to Download\r\n\r\nDownload the installer here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1\r\n\r\nAgain, this build will not auto-update. I'll post announcements here when new versions are available and you'll have to download the new version each time until I have the certificate and release servers worked out. Honestly, I'll probably write a \"check for Windows update and download\" script then you can just run that on a `// Schedule: 0 8 * * *` 😉","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-12-17T15:06:29Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/615","url":"https://gist.githubusercontent.com/johnlindquist/87e92156251d09a02154f04772f1e9bf/raw/be6dde40a7f5e1f3b8eaa9abf68d8698031cd3de/non-blocking-div.js","title":"beta.114 - Info, Settings, Choice Events 🎛","command":"beta114-info-settings-choice-events","content":"# beta.114 - Info, Settings, Choice Events\r\n\r\n## Displaying Temporary Info\r\n\r\nUntil now, `await div()` worked by waiting for the user to hit enter/escape. This still works fine, but if you want to \"timeout\" a `div` to display temporary info without user input, this entire script will run without any user interaction:\r\n\r\n[Install non-blocking-div](https://scriptkit.com/api/new?name=non-blocking-div&url=https://gist.githubusercontent.com/johnlindquist/87e92156251d09a02154f04772f1e9bf/raw/be6dde40a7f5e1f3b8eaa9abf68d8698031cd3de/non-blocking-div.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet classes = `p-5 text-3xl flex justify-center items-center text-center`\r\n\r\ndiv(`Wait 1 second...`, classes)\r\nawait wait(1000)\r\n\r\ndiv(`Just 2 more seconds...`, classes)\r\nawait wait(2000)\r\n\r\ndiv(`Almost there...`, classes)\r\nawait wait(3000)\r\n\r\n```\r\n\r\n## Remember Selection\r\n\r\nI needed to build a settings \"panel\", so I wanted to make a list that could toggle. \r\n\r\n\r\n![CleanShot 2021-11-22 at 12 08 29](https://user-images.githubusercontent.com/36073/142920816-3bf47911-578b-4e2f-9662-10257287fde4.png)\r\n\r\nThe solution was to remember the previous choice by `id`. Any time `arg` is invoked, it will check to see if a choice has an id that matched the previously submitted choice and focus back on it. This enables you to hit enter repeatedly to toggle a choice on and off.\r\n\r\n[Install remember-selection](https://scriptkit.com/api/new?name=remember-selection&url=https://gist.githubusercontent.com/johnlindquist/a86395d809c260d943f9763023f5a6f0/raw/4c1057b8500fcdf34fcd179af52f09cc7dee9ca4/remember-selection.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Off\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n]\r\n\r\nlet argConfig = {\r\n placeholder: \"Toggle items\",\r\n flags: {\r\n end: {\r\n shortcut: \"cmd+enter\",\r\n },\r\n },\r\n}\r\n\r\nwhile (true) {\r\n let item = await arg(argConfig, data)\r\n data.find(i => i.id === item.id).name =\r\n item.name === \"On\" ? \"Off\" : \"On\"\r\n\r\n if (flag.end) break\r\n}\r\n\r\nawait div(JSON.stringify(data), \"p-2 text-sm\")\r\n```\r\n\r\nYou could also use this when making a sequence of selections:\r\n\r\n[Install remember-sequence](https://scriptkit.com/api/new?name=remember-sequence&url=https://gist.githubusercontent.com/johnlindquist/80f9d005e5bff92691125f736199aa2c/raw/4e05c118bc91defe5e2f39cff20eb9862f4c6a2d/remember-sequence.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"One\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Two\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Three\",\r\n },\r\n]\r\n\r\nlet selections = []\r\n\r\nlet one = await arg(`First selection`, data)\r\nselections.push(one)\r\n\r\nlet two = await arg(\r\n {\r\n placeholder: `Second selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(two)\r\n\r\nlet three = await arg(\r\n {\r\n placeholder: `Third selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(three)\r\n\r\nawait div(\r\n selections.map(s => s.name).join(\", \"),\r\n \"p-2 text-sm\"\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n[Install no-choices-event](https://scriptkit.com/api/new?name=no-choices-event&url=https://gist.githubusercontent.com/johnlindquist/5534589a322bbb384e5bf4dbcbf00864/raw/1a7c2500149db3b8731e900646d568fa7fb5ed74/no-choices-event.js\")\r\n\r\n## Choice Events\r\n\r\n`onNoChoices` and `onChoices` allows Kit.app to tell your script when the user has typed something that filtered out every choice. Most commonly, you'll want to provide a `setHint` (I almost made it a default), but you can add any logic you want.\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\r\n {\r\n placeholder: `Pick a fruit`,\r\n onChoices: async () => {\r\n setHint(``)\r\n },\r\n onNoChoices: async input => {\r\n setHint(`No choices matched ${input}`)\r\n },\r\n },\r\n [`Apple`, `Orange`, `Banana`]\r\n)\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-22T19:09:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/587","url":"","title":"beta.98 - Previews 👀, Docs, devTools, updater improvements","command":"beta98-previews-docs-devtools-updater-improvements","content":"## Previews\r\n\r\nCreating the previews feature was a huge undertaking, but it really paid off. You can now render html into a side pane by simply providing a `preview` function. A preview can be a simple string all the way to an async function per choice that loads data based on the currently selected choice. For examples, see here #555 \r\n\r\n> You can toggle previews on/off with cmd+p\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507471-db3e4454-f0ef-4e6b-891b-bd4344a40e85.mp4\r\n\r\n## Docs\r\n\r\nAlong with previews comes the built-in docs.\r\n\r\n- Docs are built from the GitHub discussions [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) category\r\n- Each time I post/update a doc, a webhook builds the docs into a json file, Kit.app checks for a new docs.json once a day (or you can manually update them from the `Help->Download Latest Docs`\r\n- You can _click an example to install it!_ 🎉\r\n- I'll keep working on docs and examples. Please ask any questions over in the [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) section if you'd like to see something clarified.\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507953-02d44174-3ac0-43d7-8d92-4319e917d512.mp4\r\n\r\n## Dev Tools\r\n\r\nPass any data into `devTools` to pop open a Dev Tools pane so you can interact with the data. `devTools` will first log out the data, but it's also assigned to an `x` variable you can interact with in the console.\r\n\r\n> `devTools` will be another paid feature once Script Kit 1.0 releases\r\n\r\nhttps://user-images.githubusercontent.com/36073/141508954-df3ea997-a49e-4fdd-bd40-7bff76024a6d.mp4\r\n\r\n## Updater Fixes\r\n\r\nA few users reported a strange behavior with the updater. If you've had any issues with it, please download a fresh copy of Kit.app from https://scriptkit.com and overwrite the old version. There are many more guards around the updating logic to prevent those issues from cropping up again.\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T17:32:40Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/488","url":"","title":"Script Kit online on Stackblitz ⚡️","command":"script-kit-online-on-stackblitz","content":"I spent last week getting Script Kit running \"in browser\" to emulate the terminal experience over on Stackblitz. Here's a quick demo:\r\n\r\nhttps://stackblitz.com/edit/node-rnrhra?file=scripts%2Frepos-to-markdown.js\r\n\r\nThe plan is to use this to host interactive demos for the guide/docs. I'd appreciate if you could play around with it a bit and see if I missed anything.\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-10-18T20:06:28Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/457","url":"","title":"TypeScript support! 🚀","command":"typescript-support","content":"beta.62 brings with it a long-awaited, much-requested feature: TypeScript support!\r\n\r\n![CleanShot 2021-09-27 at 10 42 38](https://user-images.githubusercontent.com/36073/134951810-31754840-85c3-4ad3-a493-59c757fdda07.png)\r\n\r\n## TypeScript Support 🚀\r\n\r\n### 1. But, how?\r\n\r\nEach time your run a TS script, Script Kit will compile the TS script using `esbuild` to a JS script in a `.scripts` dir (notice the \"dot\"). The compiled JS script is then imported from there. Using `.scripts` as a sibling dir will help avoid any `import`/path issues. You can also write TS \"library\" files in your `~/.kenv/lib` dir and import them into your script just fine.\r\n\r\nIf you're experienced with `esbuild` and curious about the settings, they look like this:\r\n\r\n```js\r\nlet { build } = await import(\"esbuild\")\r\n\r\nawait build({\r\n entryPoints: [scriptPath],\r\n outfile,\r\n bundle: true,\r\n platform: \"node\",\r\n format: \"esm\",\r\n external: [\"@johnlindquist/kit\"],\r\n})\r\n```\r\n\r\nThis also opens the door to exporting/building/bundling scripts and libs as individual shippable tools which I'll investigate more in the future.\r\n\r\n### 2. Can I still run my JS scripts if I switch to TS?\r\n\r\nYes! Both your TS and JS scripts will show up in the UI.\r\n\r\n### 3. Why the `import \"@johnlindquist/kit\"`?\r\n\r\nWhen you create a new TS script, the generated script will start with the line: `import \"@johnlindquist/kit\"`\r\n\r\nThis is mostly to make your editor stop complaining by forcing it to load the type definition files and forcing it to treat the file as an \"es module\" so support \"top-level `await`\". It's not technically required since it's not technically importing anything, but your editor will certainly complain very loudly if you leave it out.\r\n\r\n### 4. Where is the setting stored?\r\n\r\nLook in your `~/.kenv/.env` for `KIT_MODE=ts`.\r\n\r\n## fs-extra's added to global\r\n\r\nThe [fs-extra methods](https://www.npmjs.com/package/fs-extra#methods) are now added on the global space. I found myself using `outputFile`, `write/readJson`, etc too often and found them to be a great addition. The only one missing is `copy` since we're already using that to \"copy to clipboard\". You can bring it in with the normal import/alias process if needed, e.g., `let {copy:fsCopy} = await import(\"fs-extra\")`\r\n\r\n## Sync Path\r\n\r\n![CleanShot 2021-09-27 at 11 10 26](https://user-images.githubusercontent.com/36073/134954703-7c9d779f-268a-4f8b-973a-59ac71eebaf0.png)\r\n\r\nYou may notice running scripts from the Script Kit app that some commands you can run in your terminal might be missing, like \"yarn\", etc.\r\n\r\nRun the following command in your terminal to copy the $PATH var from your terminal to your `~/.kenv/.env`. This will help \"sync\" up which commands are available between your terminal and running scripts from the app.\r\n\r\n```bash\r\n~/.kit/bin/kit sync-path\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-09-27T17:25:03Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/442","url":"","title":"Scripts in GitHub actions (preview)","command":"scripts-in-github-actions-preview","content":"## tl;dr Here's an example repo\r\n\r\nThe example script creates a release, downloads an image, and uploads it to the release.\r\n\r\nhttps://github.com/johnlindquist/kit-action-example\r\n\r\n## Template Repo\r\n\r\nThis page has a \"one-click\" clone so you can add/play with your own script.\r\n\r\nhttps://github.com/johnlindquist/kit-action-template\r\n\r\n## What is it?\r\n\r\nUse any of your scripts in a GitHub action. `use` the `kit-action` and point it to a scripts in your `scripts` dir:\r\n\r\n```yml\r\nname: \"example\"\r\non:\r\n workflow_dispatch:\r\n pull_request:\r\n push:\r\n branches:\r\n - main\r\n\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\" # The name of a script in your ./scripts dir\r\n```\r\n\r\n## Add env vars:\r\n\r\nYou most likely add [\"secrets\" to GitHub actions](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-an-environment), so you'll want to pass them to your scripts as environment variables:\r\n\r\n```yml\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\"\r\n env:\r\n REPO_TOKEN: \"${{ secrets.REPO_TOKEN }}\" # load in your script with await env(\"REPO_TOKEN\")\r\n```\r\n\r\n## Works with your existing repos\r\n\r\nFeel free to add this action and a `scripts` dir to your existing repos. It automatically loads in your repo so you can parse `package.json`, compress assets, or whatever it is you're looking to add to your CI.\r\n\r\n## What does \"preview\" mean?\r\n\r\nEverything is working, but it's pointing to the \"main\" branch rather than a tagged version. Once I get some feedback, I'll tag a \"1.0\" version so you can `uses: @johlindquist/kit-action@v1`\r\n\r\n## Please ask for help! 😇\r\n\r\nI'd ❤️ to help you script something for a github action! Please let me know whatever I can do to help.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-09-21T19:34:15Z"},{"menu":"Drag demo","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/405","url":"","title":"beta.55 Improved Search, Drag, and Happiness 😊","command":"beta55-improved-search-drag-and-happiness","content":"## Search Improvements\r\n\r\nbeta.55 has a vastly improved search:\r\n\r\nSearch descriptions 🎉\r\n\r\n![CleanShot 2021-08-20 at 13 37 44](https://user-images.githubusercontent.com/36073/130285547-24f111d7-a706-4be2-b0d6-b425afbe6683.png)\r\n\r\nSearch shortcuts\r\n\r\n![CleanShot 2021-08-20 at 13 51 49](https://user-images.githubusercontent.com/36073/130286634-4797c029-c2ed-4071-9e1d-285d6bf1a15f.png)\r\n\r\nSearch by kenv\r\n\r\n![CleanShot 2021-08-20 at 13 51 18](https://user-images.githubusercontent.com/36073/130286567-4fd0a155-43a7-4af5-bd3f-c0a9db9ae8dc.png)\r\n\r\nSear by \"command-name\" (if you can't think of // Menu: name)\r\n\r\n![CleanShot 2021-08-20 at 13 54 45](https://user-images.githubusercontent.com/36073/130286878-62b2a139-b3b7-4c34-904f-40d7b03a7e1c.png)\r\n\r\nSorts by \"score\" (rather than alphabetically)\r\n\r\n## Drag\r\n\r\nChoices can now take a `drag` property. This will make list items \"draggable\" and allow you to drag/drop to copy files from your machine (or even from URLs!) into any app. When using remote URLs, their will be a bit of \"delay\" while the file downloads (depending on the file size) between \"drag start\" and \"drop enabled\", so just be aware. I'll add some sort of download progress indicator sometime in the future, just not high priority 😅\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Heart Eyes (local)\",\r\n drag: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n img: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n },\r\n {\r\n name: \"React logo svg (wikipedia)\",\r\n drag: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n img: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 26 07](https://user-images.githubusercontent.com/36073/130294979-c45aabe2-6c30-41ad-94b4-64a85a2c34eb.gif)\r\n\r\nYou can use the `drag` object syntax to define a `format` and `data`\r\n\r\n> `text/html`: Renders the HTML payload in contentEditable elements and rich text (WYSIWYG) editors like Google Docs, Microsoft Word, and others.\r\n> `text/plain`: Sets the value of input elements, content of code editors, and the fallback from text/html.\r\n> `text/uri-list`: Navigates to the URL when dropping on the URL bar or browser page. A URL shortcut will be created when dropping on a directory or the desktop.\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Padding 4\",\r\n drag: {\r\n format: \"text/plain\",\r\n data: `className=\"p-4\"`,\r\n },\r\n },\r\n {\r\n name: \"I love code\",\r\n drag: {\r\n format: \"text/html\",\r\n data: `I ❤️ code`,\r\n },\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 48 00](https://user-images.githubusercontent.com/36073/130296713-6249d5c2-c01f-42d1-b2c2-ea86d2e4c29b.gif)\r\n\r\n## Happiness\r\n\r\nI'm _very_ happy with the state of Script Kit. When I started almost a year ago, I had no idea I could push the concept of creating/sharing/managing custom scripts so far. I think it looks great, feels speedy, and is flexible enough to handle so, so many scenarios.\r\n\r\nWith everything in place, next week I'm starting on creating lessons, demos, and docs. It's time to show you what Script Kit can really do 😉 \r\n\r\nP.S. - Thanks for all the beta-testing and feedback. It's been tremendously helpful!\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-08-20T21:58:48Z"},{"menu":"Flags demo","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/397","url":"https://gist.githubusercontent.com/johnlindquist/b96c8f8de9c256f909ae0f6ab0adda39/raw/9f049cf454f0766fb278e5ee7a24c6b6776df889/flags-demo.js","title":"beta.46 Design, ⚐ Flags, div, fixed notify","command":"beta46-design-flags-div-fixed-notify","content":"## Design/theme\r\n\r\nPut a lot of work into tightening up pixels and made progress towards custom themes:\r\n\r\n![CleanShot 2021-08-13 at 09 35 40](https://user-images.githubusercontent.com/36073/129383567-ae628c68-3c96-463f-a47e-4800186ea7ac.png)\r\n\r\nHere's a silly demo of me playing with theme generation:\r\n\r\nhttps://user-images.githubusercontent.com/36073/129384214-2af744ab-8165-4e3f-825d-42fadbf86aec.mp4\r\n\r\n## Flags ⚐\r\n\r\nAn astute observer would notice that the `Edit` and `Share` tabs are now gone. They've been consolidated into a \"flag menu\".\r\n\r\nWhen you press the `right` key from the main menu of script, the flag menu now opens up. This shows the selected script and gives you some options. It also exposes the keyboard shortcuts associated with those options that you can use to :\r\n\r\n![CleanShot 2021-08-13 at 09 42 52](https://user-images.githubusercontent.com/36073/129384559-bff59ebf-88d9-4b95-b9b5-640ce755fe8f.png)\r\n\r\nI've found I use `cmd+o` and `cmd+n` all the time to tweak scripts of quickly create a new one to play around with.\r\n\r\n### Custom Flags\r\n\r\nYou can pass your own custom flags like so:\r\n\r\n[Install flags-demo](https://scriptkit.com/api/new?name=flags-demo&url=https://gist.githubusercontent.com/johnlindquist/b96c8f8de9c256f909ae0f6ab0adda39/raw/9f049cf454f0766fb278e5ee7a24c6b6776df889/flags-demo.js)\r\n\r\n```js\r\n//Menu: Flags demo\r\n\r\nlet urls = [\r\n \"https://scriptkit.com\",\r\n \"https://egghead.io\",\r\n \"https://johnlindquist.com\",\r\n]\r\n\r\nlet flags = {\r\n open: {\r\n name: \"Open\",\r\n shortcut: \"cmd+o\",\r\n },\r\n copy: {\r\n name: \"Copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n}\r\n\r\nlet url = await arg(\r\n { placeholder: `Press 'right' to see menu`, flags },\r\n urls\r\n)\r\n\r\nif (flag?.open) {\r\n $`open ${url}`\r\n} else if (flag?.copy) {\r\n copy(url)\r\n} else {\r\n console.log(url)\r\n}\r\n```\r\n\r\nNotice that `flag` is a global while `flags` is an object you pass to `arg`. This is to help keep it consistent with terminal usage:\r\n\r\nFrom the terminal\r\n```bash\r\nflags-demo --open\r\n```\r\n\r\nWill set the global `flag.open` to `true`\r\n\r\n![CleanShot 2021-08-13 at 10 08 30](https://user-images.githubusercontent.com/36073/129388037-3f27a12d-9e44-4402-a51f-bac39eead54d.png)\r\n\r\n\r\nYou could also run this and pass in all the args:\r\n\r\n```bash\r\nflags-demo https://egghead.io --copy\r\n```\r\n\r\nIn the app, you could create a second script to pass flags to the first with. This is required if you need to pass multiple flags since the `arg` helper can only \"submit\" one per `arg`.\r\n\r\n```js\r\nawait run(`flags-demo https://egghead.io --copy`)\r\n```\r\n\r\nI'll put together some more demos soon. There are plenty of existing CLI tools out there using flags heavily, so lots of inspiration to pull from.\r\n\r\n## `await div()`\r\n\r\nThere's a new `div` \"component\". You can pass in arbitrary HTML. This works well with the `md()` helper which generates `html` from markdown.\r\n\r\n[Install div-demo](https://scriptkit.com/api/new?name=div-demo&url=https://gist.githubusercontent.com/johnlindquist/0ad790953f7101d313abfd48182356b0/raw/c70e17649317986707d2ac714c31afe6f7850015/div-demo.js)\r\n\r\n```js\r\n// Menu: Div Demo\r\n\r\n// Hit \"enter\" to continue, escape to exit\r\nawait div(``)\r\n\r\nawait div(\r\n md(\r\n `\r\n # Some header\r\n\r\n ## You guessed it, an h2\r\n\r\n * I\r\n * love\r\n * lists\r\n `\r\n )\r\n)\r\n\r\n```\r\n\r\n## Fixed `notify`\r\n\r\n`notify` is now fixed so that it doesn't open a prompt\r\n\r\nThe most basic usage is:\r\n\r\n```js\r\nnotify(\"Hello world\")\r\n```\r\n\r\n`notify` leverages [https://www.npmjs.com/package/node-notifier](https://www.npmjs.com/package/node-notifier)\r\n\r\nSo the entire API should be available. Here's an example of using the \"type inside a notification\":\r\n\r\n[Install notify-demo](https://scriptkit.com/api/new?name=notify-demo&url=https://gist.githubusercontent.com/johnlindquist/44387dc5b0c170e4146b061162c33532/raw/1bce77fb778a45cf9052a63d02dcab94a9cf7ef0/notify-demo.js)\r\n\r\n```js\r\n// Menu: Notify Demo\r\nlet notifier = notify({\r\n title: \"Notifications\",\r\n message: \"Write a reply?\",\r\n reply: true,\r\n})\r\n\r\nnotifier.on(\"replied\", async (obj, options, metadata) => {\r\n await arg(metadata.activationValue)\r\n})\r\n\r\n```\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-08-13T16:33:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/365","url":"","title":"beta.33 `console.log` component, cmd+o to Open, `className`","command":"beta33-consolelog-component-cmdo-to-open-classname","content":"## `console.log` Component\r\n\r\nThe follow code will create the below prompt (👀 notice the black background logging component):\r\n```js\r\nlet { stdout } = await $`ls ~/projects | grep kit`\r\n\r\nawait arg(`Select a kit dir`, stdout.split(\"\\n\"))\r\n\r\n```\r\n\r\n\r\n```js\r\nconsole.log(chalk`{green.bold The current date is:}`)\r\nconsole.log(new Date().toLocaleDateString())\r\nawait arg()\r\n```\r\n\r\n\r\n\r\nThe log even persists between prompts:\r\n\r\n```js\r\nlet first = await arg(\"First name\")\r\nconsole.log(first)\r\nlet last = await arg(\"Last name\")\r\nconsole.log(`${first} ${last}`)\r\nlet age = await arg(\"Age\")\r\nconsole.log(`${first} ${last} ${age}`)\r\nlet emotion = await arg(\"Emotion\")\r\nconsole.log(`${first} ${last} ${age} ${emotion}`)\r\nawait arg()\r\n```\r\n\r\n\r\nClick the \"edit\" icon to open the full log in your editor:\r\n![CleanShot 2021-07-22 at 16 20 57@2x](https://user-images.githubusercontent.com/36073/126716749-4eda367a-4e55-424f-915a-30207583cd3f.png)\r\n\r\n## cmd+o to Open\r\n\r\nFrom the main menu, hitting `cmd+o` will open:\r\n\r\n1. The currently selected script from the main menu\r\n2. The currently running script\r\n3. Any \"choice\" that provides a \"filePath\" prop:\r\n\r\n```js\r\nawait arg(`cmd+o to open file`, [\r\n {\r\n name: \"Karabiner config\",\r\n filePath: \"~/.dotfiles/karabiner/karabiner.edn\",\r\n },\r\n {\r\n name: \"zshrc\",\r\n filePath: \"~/.zshrc\",\r\n },\r\n])\r\n```\r\n\r\nI've found this really useful when I want to tweak the running script, but I don't want to go back through the process of finding it.\r\n\r\n## Experimental `className`\r\n\r\nYou can pass `className` into the arg options to affect the container for the list items or panel. Most classes from Tailwind should be available. Feel free to play around with it and let me know how it goes 😇:\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n `\r\n
Working on Script Kit today
\r\n `\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n [\"Eat\", \"more\", \"tacos 🌮\"]\r\n)\r\n```\r\n\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-07-22T22:44:19Z"},{"image":"https://placekitten.com/64","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/353","url":"","title":"beta.29 M1 build, install remote kenvs, polish, upcoming lessons","command":"beta29-m1-build-install-remote-kenvs-polish-upcoming-lessons","content":"I'm starting on lessons/docs on Monday. If you have anything specific you want me to cover, please reply below!\r\n\r\n## M1 Build\r\n If you're on an M1 mac, you can download the new M1 build from https://www.scriptkit.com/\r\n\r\n1. Download https://www.scriptkit.com/\r\n2. Quit Kit. *note - typing `kit quit` or `k q` in the app is the fastest way to quit.\r\n3. Drag/drop to overwrite your previous build\r\n4. Kit should now auto-update from the M1 channel\r\n5. Open Kit\r\n\r\n## Kenv Management\r\nThere are a lot of tools to help manage other kenvs. They're in the `Kit` menu and once you've installed a remote kenv (which is really just a git repo with a scripts dir), then more options show up in the `Edit` menu to move scripts between kenvs, etc. I'll cover this in detail in the docs/lessons\r\n\r\n## Polish\r\nLots of UI work:\r\n* Remembering position - Each script with a `//Shortcut` will remember its last individual prompt position. For example, if you have a script that uses `textarea`, then drag it to the upper right, the next time you launch that script, it will launch in that position.\r\n* `//Image` metadata - Scripts can now have images:\r\n```js\r\n//Image: https://placekitten.com/64\r\n```\r\nor\r\n```js\r\n//Image: logo.png\r\n```\r\nwill load from `~/.kenv/assets/logo.png`\r\n\r\n\r\n* Spinner - added a spinner for when you submit a prompt and the process needs to do some work before opening the next prompt\r\n\r\n![CleanShot 2021-07-16 at 12 22 58](https://user-images.githubusercontent.com/36073/125992326-7b6f0034-00e8-41df-9ca7-f0e33becf0b2.gif)\r\n\r\n\r\n* Resizing - *Lots* of work on getting window resizing behavior consistent between different UIs. This was a huge pain, but you'll probably never appreciate it 😅\r\n* Lots more - many more small things\r\n\r\n## Lessons!\r\n\r\nI'm starting to work on lessons next week and getting back into streaming schedule. I would ♥️ to hear any specific questions or lessons you would like to see to help you remove some friction from your day. I'll be posting the lessons over on [egghead.io](egghead.io) for your viewing pleasure. Please ask questions in the replies!\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-07-16T18:29:00Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/330","url":"","title":"Beta.20 MOAR SPEED! ⚡️","command":"beta20-moar-speed","content":"## Process Pools and Virtualized Lists\r\n\r\nhttps://user-images.githubusercontent.com/36073/123519955-7fdd8200-d66b-11eb-8167-09b9daed1c9f.mp4\r\n\r\n\r\n## Experimental `textarea`\r\n\r\nFeel free to play around with the `textarea` for multiline input.\r\n\r\n```js\r\nlet value = await textarea()\r\n```\r\n\r\nThe API of textarea will change (it currently just sets the placeholder), but it will always return the string value of the textarea, so there won't be any breaking changes if you just keep the default behavior. `cmd+s` submits. `cmd+w` cancels.\r\n\r\n## Experimental `editor` (this will become a _paid_ 💵 feature later this year)\r\n\r\nAs an upgrade to `textarea`, `await editor()` will give you a full editor experience. Same as the textarea, the API will also change, but will always return a string of the content.\r\n\r\n\r\n```js\r\n// Defaults to markdown\r\nlet value = await editor()\r\n```\r\n\r\n> ⚠️ API is subject to change!\r\n```js\r\nlet value = await editor(\"markdown\", `\r\n## Preloaded content\r\n\r\n* nice\r\n`)\r\n```\r\n\r\n```js\r\nlet value = await editor(\"javascript\", `\r\nconsole.log(\"Support other languages\")\r\n`)\r\n```\r\n\r\n### A note on paid features\r\n\r\nEverything you've used so far in the Script Kit app will stay free. The core `kit` is open-source MIT. \r\n\r\nThe paid features will be add-ons to the core experience: Themes, Editor, Widgets, Screenshots, Record Audio, and many more fun ideas. These will roll out experimentally in the free version first then move exclusively to the paid version. Expect the paid versions later this year.\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-26T17:03:40Z"},{"shortcut":"option 5","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/312","url":"","title":"Beta.19 New Features - Gotta go fast! 🏎💨","command":"beta19-new-features-gotta-go-fast","content":"Beta.19 is all about _speed_! I've finally landed on an approach I love to get the prompt moving waaaay faster.\r\n\r\nCouple videos below:\r\n\r\n## Instant Prompts\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084054-6d270a00-c79d-11eb-8b37-96473de7e0e4.mp4\r\n\r\n```js\r\n// Shortcut: option 5\r\n\r\nlet { items } = await db(async () => {\r\n let response = await get(\r\n `https://api.github.com/users/johnlindquist/repos`\r\n )\r\n\r\n return response.data\r\n})\r\n\r\nawait arg(\"Select repo\", items)\r\n\r\n```\r\n\r\n## Instant Tabs\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084134-85972480-c79d-11eb-9e18-94e5a2efa5d1.mp4\r\n\r\n## Instant Main Menu\r\n\r\nThe main menu now also leverages the concepts behind Instant Prompts listed above.\r\n\r\n## Faster in the future\r\n\r\nThese conventions laid the groundwork for caching prompt data, but I still have plenty ideas to speed things, especially around how the app launches the process. I'm looking forward to making this even faster for you!\r\n\r\nI'm also starting the work on an \"Instant Textarea\" because I know popping open a little textarea to take/save notes/ideas is something many people would use. 📝\r\n\r\n\r\n\r\n\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-07T20:47:28Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/305","url":"","title":"How to Get Your Scripts Featured on ScriptKit.com 😎","command":"how-to-get-your-scripts-featured-on-scriptkitcom","content":"TL;DR\r\n\r\n- Help -> Create kenv\r\n- Git init new kenv, push to github\r\n- Reply, dm, contact me somehow with the repo 😇\r\n\r\nHere's a video walking you through it:\r\n\r\nhttps://user-images.githubusercontent.com/36073/120856653-6732ee00-c53d-11eb-9dfb-04907b036361.mp4\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-04T20:07:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/304","url":"","title":"Beta.18 Changes/Features (`db` has a breaking change)","command":"beta18-changesfeatures-db-has-a-breaking-change","content":"## ⚠️Breaking: New `db` helper\r\n\r\n[lowdb](https://github.com/typicode/lowdb) updated to 2.0, so I updated the `db` helper to support it.\r\n\r\n* access/mutate the objects in the db directly. Then `.write()` to save your changes to the file.\r\n* `await db()` and `await myDb.write()`\r\n\r\nExample with a simple object:\r\n```js\r\nlet shoppingListDb = await db(\"shopping-list\", {\r\n list: [\"apples\", \"bananas\"],\r\n})\r\n\r\nlet item = await arg(\"Add to list\")\r\nshoppingListDb.list.push(item)\r\nawait shoppingListDb.write()\r\n\r\nawait arg(\"Shopping list\", shoppingListDb.list)\r\n```\r\n\r\n\r\nYou can also use an `async` function to store the initial data:\r\n```js\r\nlet reposDb = await db(\"repos\", async () => {\r\n let response = await get(\r\n \"https://api.github.com/users/johnlindquist/repos\"\r\n )\r\n\r\n return {\r\n repos: response.data,\r\n }\r\n})\r\n\r\nawait arg(\"Select repo\", reposDb.repos)\r\n```\r\n\r\n## Text Area prompt\r\n\r\n```js\r\nlet text = await textarea()\r\n\r\ninspect(text)\r\n```\r\n![CleanShot 2021-06-04 at 14 25 12](https://user-images.githubusercontent.com/36073/120858988-cf370380-c540-11eb-8b79-5483ec090dd8.gif)\r\n\r\n\r\n## Optional `value`\r\n\r\n`arg` choice objects used to require a `value`. Now if you don't provide a value, it will simply return the entire object:\r\n\r\n```js\r\nlet person = await arg(\"Select\", [\r\n { name: \"John\", location: \"Chair\" },\r\n { name: \"Mindy\", location: \"Couch\" },\r\n])\r\n\r\nawait arg(person.location)\r\n```\r\n\r\n## ⚗️ Experimental \"Multiple kenvs\"\r\n\r\nThere was a _ton_ 🏋️♀️ of internal work over the past couple weeks to get this working. The \"big idea\" is supporting multiple kit environments. For example:\r\n\r\n* private/personal kenv\r\n* shared kenv\r\n* company kenv\r\n* product kenv\r\n\r\n### Future plans\r\nIn an upcoming release: \r\n* you'll be able to \"click to install kenv from repo\" (just like we do with individual scripts)\r\n* update a git-controlled kenv (like a company kenv)\r\n* the main prompt will be able to search for all scripts across kenvs. \r\n* If multiple kenvs exist, creating a new script will ask you which kenv to create it in.\r\n\r\nFor now, you can try adding/creating/switching the help menu. It should all work fine, but will be _waaaay_ cooler in the future 😎\r\n\r\n![CleanShot 2021-06-04 at 11 50 32](https://user-images.githubusercontent.com/36073/120843227-16b29500-c52b-11eb-974c-a81c260b9ae2.png)\r\n\r\n## Improved Error Prompt\r\n\r\nNow when an error occurs, it takes the error data, shuts down the script, then prompts you on what to do. For example, trying to use the old `db` would result in this:\r\n\r\n![CleanShot 2021-06-04 at 12 03 04](https://user-images.githubusercontent.com/36073/120844575-d6541680-c52c-11eb-8d12-c7c3117e132e.png)\r\n\r\n## Improved Tab Switching\r\nSwitching tabs will now cancel the previous tabs' script. Previously, if you quickly switched tabs on the main menu, the \"Hot\" tab results might show up in a different tab because the loaded _after_ the tab switched. The internals around message passing between the script and the app now have a cancellation mechanism so you only get the latest result that matches the prompt/tab. (This was also a ton of internals refactoring work 😅)\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-04T18:09:55Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/282","url":"","title":"✨NEW FEATURES✨ beta.17","command":"new-features-beta17","content":"New features are separated into the comments below:\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-05-18T20:24:57Z"},{"background":"true","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/245","url":"","title":"✨ NEW ✨ // Background: true","command":"new-background-true","content":"`beta.12` brings in the ability to start/stop background tasks.\r\n\r\n\r\nUsing `// Background :true` at the top of your script will change the behavior in the main menu:\r\n```js\r\n// Background: true\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## Auto (like nodemon)\r\n```js\r\n// Background: auto\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\nUsing `auto`, after you start the script, editing will stop/restart the script.\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-05-06T19:36:22Z"},{"watch":"~/projects/thoughts/**/*.md","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/213","url":"","title":"// Watch: metadata 👀","command":"watch-metadata","content":"Script Kit now supports `// Watch:` metadata\r\n\r\n```js\r\n// Watch: ~/projects/thoughts/**/*.md\r\n\r\nlet { say } = await kit(\"speech\")\r\n\r\nsay(\"journal updated\")\r\n```\r\n\r\n* `// Watch: ` supports any file name, glob, or array (Kit will `JSON.parse` the array).\r\n* Scripts will run on the \"change\" event\r\n* Read more about supported [globbing](https://github.com/micromatch/picomatch#globbing-features)\r\n\r\n> Read about the [other metadata](https://github.com/johnlindquist/kit/discussions/185)\r\n\r\nI would _LOVE_ to hear about scenarios you would use this for or if you run into any issues 🙏","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-29T14:31:31Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/150","url":"","title":"beta.96 - Design, Drop, and Hotkeys! Oh my!","command":"beta96-design-drop-and-hotkeys-oh-my","content":"\r\nhttps://user-images.githubusercontent.com/36073/115079813-fc5f2200-9ebe-11eb-8e7c-74c8a1d2aee3.mp4\r\n\r\nCan't wait to see what you build! Happy Scripting this weekend! 😇","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-16T20:32:44Z"},{"menu":"Google Image Search","description":"","author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/119","url":"https://gist.githubusercontent.com/johnlindquist/99756d4e1a54c737dc534c4edb5f6c9d/raw/55c440503a8a653c3ef3dafb9ba1bd567fc0b14a/google-image-search.js","title":"*New* Choice Preview","command":"new-choice-preview","content":"\r\nhttps://user-images.githubusercontent.com/36073/114220248-fc907800-9928-11eb-8096-61a5debbdc0d.mp4\r\n\r\n\r\n[Install google-image-search](https://scriptkit.app/api/new?name=google-image-search&url=https://gist.githubusercontent.com/johnlindquist/99756d4e1a54c737dc534c4edb5f6c9d/raw/55c440503a8a653c3ef3dafb9ba1bd567fc0b14a/google-image-search.js)\r\n\r\n```js\r\n// Menu: Google Image Search\r\n// Description: Searches Google Images\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nlet gis = await npm(\"g-i-s\")\r\n\r\nlet selectedImageUrl = await arg(\r\n \"Image search:\",\r\n async input => {\r\n if (input.length < 3) return []\r\n\r\n let searchResults = await new Promise(res => {\r\n gis(input, (_, results) => {\r\n res(results)\r\n })\r\n })\r\n\r\n return searchResults.map(({ url }) => {\r\n return {\r\n name: url.split(\"/\").pop().replace(/\\?.*/g, \"\"),\r\n value: url,\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\ncopy(selectedImageUrl)\r\n\r\n```\r\n\r\n\r\n\r\n[Install giphy-search](https://scriptkit.app/api/new?name=giphy-search&url=https://gist.githubusercontent.com/johnlindquist/dc17a3f07fb41b855e742a0f995cb0ed/raw/109831f9d40a8293b7d8741b44081fddcb024cda/giphy-search.js)\r\n\r\n```js\r\n// Menu: Giphy\r\n// Description: Search giphy. Paste markdown link.\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\nlet download = await npm(\"image-downloader\")\r\nlet queryString = await npm(\"query-string\")\r\nlet { setSelectedText } = await kit(\"text\")\r\n\r\nif (!env.GIPHY_API_KEY) {\r\n show(\r\n `
\r\n
\r\n Grab an API Key from the Giphy dev dashboard:\r\n
`\r\n )\r\n}\r\nlet GIPHY_API_KEY = await env(\"GIPHY_API_KEY\")\r\n\r\nlet search = q =>\r\n `https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${q}&limit=10&offset=0&rating=g&lang=en`\r\n\r\nlet { input, url } = await arg(\r\n \"Search giphy:\",\r\n async input => {\r\n if (!input) return []\r\n let query = search(input)\r\n let { data } = await get(query)\r\n\r\n return data.data.map(gif => {\r\n return {\r\n name: gif.title.trim() || gif.slug,\r\n value: {\r\n input,\r\n url: gif.images.downsized_medium.url,\r\n },\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\nlet formattedLink = await arg(\"Format to paste\", [\r\n {\r\n name: \"URL Only\",\r\n value: url,\r\n },\r\n {\r\n name: \"Markdown Image Link\",\r\n value: `![${input}](${url})`,\r\n },\r\n {\r\n name: \"HTML \",\r\n value: ``,\r\n },\r\n])\r\n\r\nsetSelectedText(formattedLink)\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-09T21:43:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/112","url":"https://gist.githubusercontent.com/johnlindquist/f238cb1b3a3ed97890657ccf154d12b1/raw/a488a8b6c331d527bb0433a6b8df9428263b85a0/link-kit.js","title":"Types are here!","command":"types-are-here","content":"Update (1.1.0-beta.86) adds a [`~/.kit/kit.d.ts`](https://github.com/johnlindquist/kit/blob/main/kit.d.ts) to allow better code hinting and completion.\r\n\r\n❗️After updating, you will need to manually \"link\" your `~/.kenv` to your `~/.kit` for the benefits (This will happen automatically for new users during install)\r\n\r\nMethod 1 - Install and run this script\r\n\r\n[Click to install link-kit](https://scriptkit.app/api/new?name=link-kit&url=https://gist.githubusercontent.com/johnlindquist/f238cb1b3a3ed97890657ccf154d12b1/raw/a488a8b6c331d527bb0433a6b8df9428263b85a0/link-kit.js)\r\n\r\n```js\r\nawait cli(\"install\", \"~/.kit\")\r\n```\r\n\r\nMethod 2 - In your terminal\r\n```bash\r\nPATH=~/.kit/node/bin ~/.kit/node/bin/npm --prefix ~/.kenv i ~/.kit\r\n```\r\n\r\nNow your scripts in your `~/.kenv/scripts` should have completion/hinting for globals included in the \"preloaded\" scripts.\r\n\r\n> I still need to add types for the helpers that load scripts from dirs `kit()`, `cli()`, etc.\r\n\r\nPlease let me know how it goes and if you have any questions. Thanks!","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-03T18:08:24Z"}]
+[{"pass":"true","name":"Pass Demo","snippet":"lorem,,","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1365","url":"","title":"Script Kit 2.0 Release Candidate","command":"script-kit-20-release-candidate","content":" # Script Kit 2.0 Release Candidate\r\n\r\nDownload for your platform from the releases page:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.99.82\r\n\r\n## New Main Menu\r\n\r\nThe main menu has been re-designed and optimized for your keyboard-centric workflows.\r\n\r\n\r\n\r\n## New Custom OSX Window\r\n\r\nPrompts like `editor` and `term` which use `ignoreBlur` will now convert the window into an \"app\" window which can be accessed by `cmd+tab`, is pinned when switching spaces, and shows up in Mission Control. This is also forward-looking to v3 which I'll talk about more in the future...\r\n\r\n## Keywords\r\nBorrowed directly from Alfred, keywords allow you to trigger a script by typing a keyword followed by a space.\r\n\r\nA keyword can be a single character or a word. For example, `c` for `clipboard`.\r\n\r\n### Built-in Keywords\r\n- `s` - Displays Snippets from your Snippets Directory\r\n- `c` - Displays Clipboard History\r\n- `f` - Find script by searching script contents\r\n- `kit` - Access kit settings\r\n- `kenv` - Access kenv options\r\n- `npm` - Add/remove npm packages\r\n- `spell` - List spelling suggestions for the input\r\n \r\n And many others. To see them all, type `keyword` into the Script Kit prompt.\r\n\r\n To create a custom keyword for your own scripts, add:\r\n\r\n ```\r\n // Keyword: mycustomkeyword\r\n ```\r\n\r\n If the first prompt is an `await arg()`, it will display the list in the main menu. Otherwise, it will jump to the next prompt.\r\n\r\n Also, if the first prompt is a static list and you shave off the few milliseconds required to load the list from your script, you can cache the list (after the first run) by adding:\r\n\r\n ```\r\n // Cache: true\r\n ```\r\n\r\nUse caching sparingly, as it's only useful for `// Keyword` and `// Shortcut` where the first prompt is an `arg` with a static list.\r\n\r\n## Pass Main Menu Input to a Script\r\n\r\n```\r\n// Pass: true\r\n```\r\n\r\nThe `Pass` metadata allows you to type into the main menu without your script being filtered out of the list. Once you select the script, the input you've typed will be \"passed\" into the script. You can access the \"passed\" input using `flag?.pass`. For example, if you want to pass the main menu input to the input of you first arg:\r\n\r\n```\r\n// Name: Pass Demo\r\n// Pass: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg({\r\n input: flag?.pass || \"\",\r\n})\r\n```\r\n\r\n### Pass \"postfix\"\r\n\r\nImage you want to type: \"How many moons does Jupiter have?\" and have the \"?\" automatically trigger a script that reaches out to an AI service. You can do this by adding a \"postfix\" to the `Pass` metadata.\r\n\r\n```\r\n// Pass: ?\r\n```\r\n\r\nThis will automatically trigger the script when you type a \"?\" at the end in the main menu and the entire input will be passed to the script as `flag?.pass`.\r\n\r\n## Multi-select Prompt `await select()`\r\n\r\n\r\n\r\n```js\r\nlet arrayOfChoices = await select(\"Pick one or more\", [\"one\", \"two\", \"three\"])\r\n```\r\n\r\nProbably the most requested feature, Script Kit now has a `select` prompt which allows you to pick multiple choices.\r\n\r\n## Themes Per Light/Dark Mode\r\n\r\nYou can now customize which theme will be active for both light and dark more or use the same theme for both.\r\n\r\n## Snippets Directory\r\n\r\nIn your ~/.kenv/snippets directory, create .txt files you want to use as snippets. For example:\r\n1. Create `~/.kenv/snippets/lorem.txt`\r\n2. Add the following text to the file:\r\n```\r\n// Name: Lorem Ipsum\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\r\n```\r\n\r\nThe \"Lorem Ipsum\" snippet will now appear in the Snippets menu using the `s` keyword. Select the snippet will paste it into the current application.\r\n\r\nAdding Snippet metadata will allow you to invoke the snippet anywhere on your system. It's a best practice to use a \"postfix\", such as \",,\", to avoid triggering the snippet when you don't want to.\r\n```\r\n// Snippet: lorem,,\r\n```\r\n\r\n### Snippet Creator\r\n\r\n\r\n\r\nTyping `ns ` from the main menu will launch the quick snippet creator. There's a helpful guide there that will show all the available options. Just to list a few:\r\n\r\n- Tab stops: $0, $1, $2, $3\r\n- Dropdown list: ${|apple,banana,cherry|}\r\n- Current selection: $SELECTION\r\n- Clipboard content: $CLIPBOARD\r\n\r\nand many more...\r\n\r\nIf you need more advanced scenarios, for example clipboard history:\r\n\r\n```js\r\nlet history = await getClipboardHistory()\r\nlet text = await arg(\"Pick an item from your history\", history.map(item => item.value)\r\n\r\nlet result = await template(`Hello $0,\r\n\r\n${text}\r\n`)\r\n\r\nawait writeFile(home(\"example.txt\"), result)\r\n```\r\n\r\n## Actions\r\n\r\nThe new `actions` array makes it much easier to assign shortcuts and behaviors when you have a choice selected. You can access the actions menu by pressing `right`on the keyboard, then quickly search to execute the action you want to take with the currently focused choice:\r\n\r\n```js\r\n// Name: Actions Example\r\n\r\nimport { Action } from \"@johnlindquist/kit\"\r\n\r\n// Using a \"flag\" determines where to do to custom logic: After the prompt or in the action\r\n// Also, \"flags\" are supported when running the script in the terminal with `--js`\r\nlet actions: Action[] = [\r\n {\r\n shortcut: `${cmd}+t`,\r\n name: \"Append .ts\",\r\n visible: true, // Display shortcut in the prompt\r\n onAction: async (input, state) => {\r\n // Since we're not using a \"flag\", we can do custom logic here\r\n submit(`${state.focused?.name}.ts`)\r\n },\r\n },\r\n {\r\n name: \"Append .js\",\r\n flag: \"js\", // Set `global.flag.js` to true when selecting the action\r\n },\r\n]\r\n\r\nlet result = await arg(\"Pick a number\", [\"one\", \"two\", \"three\"], actions)\r\n\r\nif (flag.js) {\r\n result = `${result}.js`\r\n}\r\n\r\ninspect(result)\r\n```\r\n\r\n## Previews on All Prompts\r\n\r\nYou can build-out helpful previews on the right side for all prompts now (see the Snippet Creator above for a great example). This is especially useful for prompts like `editor`, `drop`, and `term` where you may need reminders on what the script is doing or steps you want to guide the user through.\r\n\r\n```js\r\nawait editor({\r\n value: `Book Outline`,\r\n preview: md(`# Requirements\r\n- Cover Major Characters\r\n- Address the theme\r\n- Have a beginning, middle, and end \r\n `),\r\n})\r\n```\r\n\r\n\r\n## Move kenv to a Different Directory\r\n\r\nType `kit move` into the main prompt to move your kenv directory to a different location. This is useful if you want to keep your scripts in dotfiles or other setup.\r\n\r\n\r\n## onClick and onKeydown Globals\r\n- Global event handlers for click and type events.\r\n\r\n## Custom TypeScript Loader\r\n\r\nThe new custom loader eliminates the need to watch .ts files for changes (which caused many issues from a long-running app). We now have a customized loader that can run any TypeScript file instantly.\r\n\r\nAn awesome benefit of this is you can create a script anywhere on your system, such as `~/Desktop/my-script.ts` (or js if you prefer) and run it from script runner: `~/.kit/bin/kit ~/Desktop/my-script.ts`.\r\n\r\n> Note: Your IDE won't recognize the `import` statements since it's not in a project, but the script will run just fine.\r\n\r\nIf we get enough requests, we will allow you to run the scripts from the built-in file browser through the app as well.\r\n\r\n\r\n## Watch Scripts for npm Install\r\n\r\nAs you're writing your scripts in your favorite editor, Kit.app will detect if you've added a new npm package and automatically prompt you to install it using the Kit.app built-in terminal.\r\n\r\n\r\n## Road to v2\r\n\r\nThe plan is to release v2 in mid-December. I'll be focusing on updating documentation, guides, tips, and tricks to finally help everyone get a more complete grasp of what Script Kit is capable of. There are a few more APIs which haven't been announced which will be included as well.\r\n\r\n\r\n## Questions?\r\n\r\nPlease ask below. We're always happy to help figure out better workflows to optimize your day. Happy scripting!\r\n\r\n\\- John","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-10-27T07:04:16Z"},{"group":"Favorite","name":"Group Choices","cache":"true","pass":"true","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1289","url":"","title":"Script Kit 1.76.10 - July 2023 Release - Searching Enhancements, Grouping Logic, More Choice Options","command":"script-kit-17610-july-2023-release-searching-enhancements-grouping-logic-more-choice-options","content":"# Script Kit 1.76.10 - July 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist\r\n\r\n## Features\r\n\r\n### Windows arm64 Build\r\n\r\nWe now have \"arm64\" builds for Windows: https://github.com/johnlindquist/kitapp/releases/tag/v1.76.10\r\n\r\n> arm64 Windows limitations: Unfortunately, the \"keyboard\" libraries for Windows are not yet available for arm64. So, you won't be able to use snippets, \"setSelectedText\", and other keyboard-related features. I'd recommend AutoHotkey as a workaround until the libraries are available.\r\n\r\n### Scripts Caching\r\n\r\nInstead of waiting for the main menu to parse/load the scripts, the previous scripts results are now cached. This shaves off the ~100ms delay when opening the main menu from the keyboard shortcut. (There are also some clever technical tricks under the hood that would require a blog post to explain)\r\n\r\n> Note: If you have a script that uses a shortcut which displays choices that don't change often, consider adding the `// Cache: true` metadata to the script for that extra 100ms boost.\r\n\r\n\r\n### Scripts Grouping and `groupChoices`\r\n\r\nUse the `//Group` metadata to group scripts together in the main menu. For example:\r\n\r\n```js\r\n// Group: Favorite\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png)\r\n\r\nThe you can search for \"Favorite\" from the menu to filter to the scripts with that metadata.\r\n\r\n\r\nYou can also add the `.group` property to any choice, then pass your choices to `groupChoices` to use the grouping behavior in your own scripts.\r\n\r\n```js\r\n// Name: Group Choices\r\n// Cache: true\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { faker } from \"@faker-js/faker\"\r\nimport _ from \"lodash\"\r\n\r\nlet faang = [\"Facebook\", \"Apple\", \"Amazon\", \"Netflix\", \"Google\"]\r\n\r\nlet people = Array.from({ length: 25 }).map(() => {\r\n let name = faker.person.fullName()\r\n return {\r\n name,\r\n value: name,\r\n description: faker.color.human(),\r\n group: _.sample(faang), // Grouping by a random company\r\n }\r\n})\r\n\r\nlet groupedChoices = groupChoices(people)\r\n\r\nlet result = await mini(\"Arg Group Demo\", groupedChoices)\r\nawait editor(result)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png)\r\n\r\n### Flags \"Submenu\"\r\n\r\nWhen using `flags`, pressing \"right\" (or `cmd+k`) now displays the available actions (AKA \"flags\") to the side in a separate searchable list with its own state.\r\n\r\nThis is a first pass at a \"submenu\" feature. We'll explore nested submenus in the future.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png)\r\n\r\n### Searching Off-loaded to Main Process\r\n\r\nPreviously, the searching logic was done in the renderer process. This creating some technical limitations on how we could pass results from a script to the list.\r\n\r\nNow, you will be able pass \"scored\" choice results directly from the script to the list enabling you move the search logic to your script/server/API and display highlighted \"scored\" choices in the list. More information on this in the future.\r\n\r\n### `// Pass` and `.pass` Metadata\r\n\r\nThe `// Pass` metadata on a script (and `.pass` property on a choice) will pass the current input of the prompt directly to the script so you can type input from the main menu that will be passed to the script.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png)\r\n\r\n```js\r\n// Name: Grocery List\r\n// Pass: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet note = arg?.pass || (await arg(\"Enter an item\"))\r\n\r\nawait editor(note)\r\n```\r\n\r\nYou can also do this with your own choices by using the `.pass` property on a choice and using the `groupChoices` (as discussed above) to bring up the more advanced list.\r\n\r\n### Choice `.miss` Property\r\n\r\nAdding `.miss` to a choice will make the choice only appear if the search doesn't match any other choices. This is useful for showing a \"No Results\" choice:\r\n\r\n```js\r\n// Name: Miss Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait mini(\"Select an Item\", [\r\n \"One\",\r\n \"Two\",\r\n {\r\n name: \"No Choices Available\",\r\n miss: true,\r\n disableSubmit: true,\r\n nameClassName: `text-red-500`,\r\n },\r\n])\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png)\r\n\r\n## Choice `.skip` Property\r\n\r\nUsing `.skip` allows you to create a choice that can't be searched/selected:\r\n\r\n> Note: This is used internally for the \"groupChoice()\" headers\r\n\r\n```js\r\n// Name: Separator Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"A demo for skip\", [\r\n \"Apple\",\r\n \"Banana\",\r\n \"Cherry\",\r\n {\r\n name: \"Separator\",\r\n height: PROMPT.ITEM.HEIGHT.XXXS,\r\n html: ``,\r\n skip: true,\r\n },\r\n \"Celery\",\r\n \"Cucumber\",\r\n \"Lettuce\",\r\n])\r\n```\r\n\r\n## Thanks for Using Script Kit!\r\n\r\nHappy Scripting!\r\n\r\n\\- John Lindquist","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-28T00:11:40Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1256","url":"","title":"Script Kit 1.59.1 - May 2023 Release","command":"script-kit-1591-may-2023-release","content":"# Script Kit 1.59.1 - May 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Sunsetting Features\r\n\r\nWe're \"sunsetting\"/not supporting a few features going forward. \r\n\r\n> The `npm` and `db` apis will still be available (they're used internally in the SDK!), but we won't be actively supporting them\r\n\r\n1. `await npm()`. Use `await import()` or regular `import` statements instead.\r\n\r\nScript Kit now recommends using standard import methods. When you attempt to run a script and an import fails, Script Kit will catch the error and prompt you to install it. This removes the need for `await npm()`. This is especially useful for people using TypeScript as you don't need to worry about typing `npm` anymore.\r\n\r\n2. `await db()`. We now recommend `keyv`: [https://www.npmjs.com/package/keyv](https://www.npmjs.com/package/keyv)\r\n\r\n`keyv` is a much better solution for storing/retrieving data and achieves all of the future features we had planned for `db`.\r\n\r\nWe'll do our best to update older demos/tutorials that use these methods as we're able.\r\n\r\n## Previews Everywhere\r\n\r\nEvery prompt type (`term`, `drop`, `fields`, etc) now supports the `preview` key. Pass in HTML to display it to the right side of your prompt. This is great for instructions and guiding the user through each script:\r\n\r\n> Note: The `md()` helper is often used to transform markdown into HTML\r\n\r\n\r\n```js\r\nawait term({\r\n preview: md(`# Follow these steps:\r\n\r\n1. First, type \\`ls\\` to see the files in this directory\r\n2. Then, type \\`cat \\` to see the contents of a file\r\n3. Finally, type \\`exit\\` to exit the terminal\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n```js\r\nawait drop({\r\n placeholder: \"Drop an mp3\",\r\n preview: md(`# Convert mp3 to wav\r\n\r\n## Instructions\r\n\r\n1. Drag and drop an mp3 file\r\n2. A progress prompt will open then close when ready\r\n3. Your .wav will be created next to the .mp3 file\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n## `term.write()`\r\n\r\nWhen a `term` prompt is open, you can issue terminal commands from shortcuts, etc by using `term.write(myCommand)`\r\n\r\n```js\r\nawait term({\r\n shortcuts: [\r\n {\r\n name: \"List files\",\r\n key: `${cmd}+l`,\r\n onPress: () => {\r\n let command = isWin ? \"dir\" : \"ls\"\r\n term.write(command)\r\n },\r\n bar: \"right\",\r\n },\r\n {\r\n name: \"Up a Directory\",\r\n key: `${cmd}+u`,\r\n onPress: () => {\r\n term.write(\"cd ..\")\r\n },\r\n bar: \"left\",\r\n },\r\n {\r\n name: \"Clear\",\r\n key: `${cmd}+k`,\r\n onPress: () => {\r\n let command = isWin ? \"cls\" : \"clear\"\r\n term.write(command)\r\n },\r\n bar: \"left\",\r\n },\r\n ],\r\n})\r\n```\r\n\r\n\r\n## Kenv Improvements:\r\n\r\n### \"New Kenv\" prompt for GitHub repo:\r\n\r\nWhen creating a new Kenv, it will guide you through the process of linking the Kenv to a remote GitHub repo:\r\n\r\n\r\n\r\n\r\n### With a script selected, take Kenv actions\r\n\r\nPress \"right\" (or `cmd+k`) with a script selected to reveal many \"Kenv\" actions:\r\n\r\n\r\n\r\n### Push/Pull From Remote Kenv\r\n\r\nFrom a script (or a \"Manage Kenv\"), you can now push/pull changes as it swaps you over to a terminal to take action:\r\n\r\n\r\n\r\n### \"Trusted\" Kenvs\r\n\r\nThanks to Script Kit + AI integrations, we've had a large influx of \"non-developer\" users. This necessitated more warnings/protections around sharing scripts.\r\n\r\nSome scripts have features that run scripts automatically: Shortcuts, schedule, background, etc. These kenvs now need to be \"Trusted\" to enable these features to add an extra layer of protection against bad actors. PLEASE only use scripts from people you absolutely trust.\r\n\r\nYou can \"trust\" a kenv during new/clone setup, or later from the Kit tab->Manage Kenv menu. You can also \"untrust\" a kenv from the same menu.\r\n\r\n\r\n\r\n## \"Trigger\" flag\r\n\r\nIf a script is run by \"schedule\", \"shortcut\", etc, you can now access what triggered it by using\r\n\r\n```js\r\nif(flag?.trigger === \"schedule\") // do something specific\r\n```\r\n\r\nThis will allow you customize the behavior based on whether you invoked it manually or automatically\r\n\r\n## Windows Fixes\r\n\r\nHandled edge-cases around\r\n\r\n- Windows setup/install process\r\n- Windows usernames that include spaces\r\n- Windows terminal fixes\r\n\r\nThanks to all the Windows bug reports and testers on Discord. Please keep them coming ❤️\r\n\r\n## Node 18.15.0\r\n\r\nWe're now on node 18.15.0. View the CHANGELOG: https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V18.md#18.15.0\r\n\r\nHappy Scripting - John Lindquist","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-15T18:49:05Z"},{"name":"Transcribe Mic","https":"//github.com/openai/openai-node/issues/77#issuecomment-1463150451","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1178","url":"","title":"Script Kit 1.53.22 - April 2023 Release","command":"script-kit-15322-april-2023-release","content":"# Script Kit 1.53.22 - April 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Features\r\n\r\n### `await mic()`\r\n\r\nUsing await mic will return a buffer of the audio recorded from your microphone.\r\n\r\n```js\r\n// Name: Transcribe Mic\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Configuration, OpenAIApi } = await import(\"openai\")\r\n\r\nlet config = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n})\r\n\r\nlet openai = new OpenAIApi(config)\r\n\r\nlet data = await mic()\r\n\r\nlet stream = Readable.from(data)\r\n// https://github.com/openai/openai-node/issues/77#issuecomment-1463150451\r\nstream.path = \"speech.webm\"\r\n\r\n// If you're confused by \"createTranscription\" params, see: https://github.com/openai/openai-node/issues/93#issuecomment-1471285341\r\nlet response = await openai.createTranscription(stream, \"whisper-1\")\r\n\r\nlet transcriptionPath = tmpPath(`${Date.now()}.txt`)\r\nawait writeFile(transcriptionPath, response.data.text)\r\n\r\nawait editor(response.data.text)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png)\r\n\r\n\r\n### `await webcam()`\r\n\r\nUsing await webcam will return a buffer of the image captured from your webcam.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png)\r\n\r\n```js\r\n// Name: Selfie\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await webcam()\r\n\r\nlet filePath = tmpPath(\"webcam.png\")\r\n\r\nawait writeFile(filePath, buffer)\r\nawait revealFile(filePath)\r\n```\r\n\r\n### Resizing\r\n\r\nThe prompt will now grow and shink to match the length of the list of options.\r\n\r\nYou can also manually control the size of prompts by using the new `PROMPT` constants:\r\n\r\n```js\r\nawait editor({\r\n width: PROMPT.WIDTH[\"5XL\"],\r\n height: PROMPT.HEIGHT[\"5XL\"],\r\n})\r\n```\r\n\r\n### Terminal \"Close on Exit\"\r\n\r\nThe terminal will now automatically close when you exit the command.\r\n\r\n> Note: By default, it start a shell (zsh/bash/cmd.exe for mac/linux/windows), so you'll need to run `&& exit` after your command\r\n\r\n```js\r\nawait term(\"brew install ffmpeg && exit\")\r\n```\r\n\r\nYou can also use the `shell` option to disable the shell and just run the tool directly:\r\n\r\n```js\r\nawait term({\r\n shell: false,\r\n command: \"brew\",\r\n args: [\"services\", \"restart\", \"yabai\"],\r\n closeOnExit: false, // optional, if you want to view output before it closes\r\n})\r\n```\r\n\r\n> Note: Also fixed some resizing bugs with the terminal.\r\n\r\n### Windows App Launcher\r\n\r\nPress `;` from the main menu to launch the App Launcher.\r\n\r\nThanks to @dodgez: https://github.com/johnlindquist/kit/pull/1161\r\n\r\n> Note: We still need a Linux App Launcher if anyone wants to take a stab at it :)\r\n\r\n### Custom Fonts\r\n\r\nYou can now use custom fonts with the UI. In your `~/.kenv/.env` file, add:\r\n\r\n```bash\r\nKIT_MONO_FONT=\"Menlo\"\r\nKIT_SANS_FONT=\"Arial\"\r\n```\r\n\r\n### Experimental: Shebang Scripts 🎉\r\n\r\nAdd a script in your `~/.kenv/scripts` directory with a shebang:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png)\r\n\r\n```bash\r\n#!/bin/bash\r\nopen \"https://scriptkit.com\"\r\n```\r\n\r\nThen run it from the main menu\r\n\r\n### Experimental: Windows `.bat` Files\r\n\r\nMac and Linux users have always had executables they could run in the terminal in the `~/.kenv/bin` dircetory. Now, on Windows, Script Kit will generate `.bat` files associated with each script that you can run from the terminal.\r\n\r\n### Info Choices\r\n\r\nWe now have a custom \"info\" choice that isn't selectable, but shows up in the list to present the user with additional information about the input/list:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png)\r\n\r\n```js\r\n// Name: Testing Info OnNoChoices\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"Select a number\",\r\n [\r\n {\r\n name: \"Sorry, no matches\",\r\n info: \"onNoChoices\", // or \"always\"\r\n },\r\n \"one\",\r\n \"two\",\r\n \"three\",\r\n ]\r\n)\r\n```\r\n\r\n### Removing `npm` requirement\r\n\r\nWhen you run a script with a missing module, it will catch the error and prompt you to install it. This removes the requirement of the `await npm()`, but you can still use it if you find it more convenient.\r\n\r\nThe examples and scripts that I share from now on will no longer use `await npm()`, but I have no plans to deprecate it.\r\n\r\n## Fixes\r\n\r\n- Lots of little improvements around the `chat` component.\r\n- Some solid performance improvements\r\n- Upgrading to lots of the latest libraries\r\n\r\n\r\n## Call for Help: Disable Window Animation on Windows?\r\n\r\nIf anyone knows how to disable the window animation on Windows (the one that transparently zooms in when you open a window), please let me know because I think it's super annoying and I'd love to disable it in the app. For now, I strongly recommend disabling it globally on Windows:\r\n\r\n1. Open the Settings app by pressing the Windows key + I.\r\n2. Click on “Ease of Access”.\r\n3. Scroll down to “Other options” and click on “Visual options”.\r\n4. Under “Play animations in Windows”, toggle the switch to “Off”.\r\n\r\n## One Last Thing...\r\n\r\nScript Kit AI course part 1 dropping this week... 💥","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-25T22:46:01Z"},{"name":"Chat Hello World","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1148","url":"https://gist.githubusercontent.com/johnlindquist/609d3f2dcbc49911698c3c2162310c0d/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts","title":"Script Kit v1.51.2 - Welcoming our AI Overlords! 👑","command":"script-kit-v1512-welcoming-our-ai-overlords","content":"# Script Kit v1.51.2 - March 2023 Release\r\n\r\nDownload from https://www.scriptkit.com/\r\nJoin our Discord: https://discord.gg/qnUX4XqJQd\r\n❤️ Sponsor Script Kit development [https://github.com/sponsors/johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205) ❤️\r\n## Features\r\n\r\n### Build AI Conversations with the New `chat` Prompt\r\n\r\nYou can use the new `chat` prompt to create a chat interface for your scripts. The `chat` prompt returns a `messages` array that you can inspect to see the messages that were sent and received.\r\n\r\nIt supports keyboard navigation so you can press the up/down arrow keys to navigate through the messages and copy/paste them. You can also use `shortcuts` the same way you would with other prompts.\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/609d3f2dcbc49911698c3c2162310c0d/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n```\r\n\r\nThe `kit-examples` repo (bundled with new installs) has an example of using the latest ChatGPT model:\r\nhttps://github.com/johnlindquist/kit-examples/blob/main/scripts/chatgpt.js\r\n\r\n✨ Want more AI example scripts? Check out this post 👀: https://github.com/johnlindquist/kit/discussions/1143 ✨\r\n\r\n### The `eyeDropper` Color Picker\r\n\r\nYou can now use the `eyeDropper` function to pick a color from anywhere on your screen. It returns an object with `sRGBHex` to align with Chrome's Eyedropper tool.\r\n\r\n> Note: Unfortunately, windows restricts color picking to the foreground window. Still investigating a workaround.\r\n\r\n\r\n[Open testing-get-color in Script Kit](https://scriptkit.com/api/new?name=testing-get-color&url=https://gist.githubusercontent.com/johnlindquist/e10cc5dfc08fd5c9824bb3a7e5e50071/raw/76bb4b86f08e1f2a8b2f09d6e2ba47286dc42673/testing-get-color.ts\")\r\n\r\n```js\r\n// Name: Testing Get Color\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide()\r\nlet value = await eyeDropper()\r\nawait editor(value.sRGBHex)\r\n\r\n```\r\n\r\n### Node 18.12.1\r\n\r\nScript Kit now bundles Node 18.12.1. (Previous scripts ran on Node 16.17.2).\r\n\r\n### New Settings in `~/.kit/db/app.json` Features\r\n\r\n- \"mini\" boolean - Use Script Kit in a much smaller window\r\n- \"termFont\" string - Set the font for the terminal\r\n- \"cachePrompt\" boolean - If you experience issues with the prompt position caching logic, disable it so is _always_ show in the center of the screen\r\n- \"convertKeymap\" boolean - Script Kit auto converts to your selected OS keymap (dvorak, colemak, etc). There have been a few reports of this conflicting with Portuguese keyboards. If you experience issues, disable this setting.\r\n- \"searchDebounce\" boolean - by default, Script Kit will debounce when your choices list has over >1000 items. It's mostly a precaution and you can disable it if you'd like.\r\n\r\nThe defaults are as follows:\r\n```json\r\n{\r\n \"mini\": false,\r\n \"termFont\": \"monospace\",\r\n \"cachePrompt\": true,\r\n \"convertKeymap\": true,\r\n \"searchDebounce\": true\r\n}\r\n```\r\n\r\n## Fixes\r\n\r\n- Fixed a bug where `theme` was reset to the default when waking from sleep\r\n- Fixed the `Open in Script Kit` urls for Windows users\r\n- Fixed Calculator sizing from main menu\r\n- `await npm(\"package-name\")` will now mark a package as an \"external\" dependency in TypeScript so you can use import statements for packages that aren't installed yet and your script will still compile\r\n- Other minor bug fixes\r\n- Fixed installation issues when dealing with certs/proxies\r\n\r\n## Experimental\r\n\r\n### `toast()`\r\n\r\nYou can try out the experimental `toast(\"Copied\")` feature. I'm still working out the API, so there's no \"onClick\" or anything, but you should be able to pass the other toast properties (this uses `react-toastify`) explained [here](https://fkhadra.github.io/react-toastify/dispatch-toast-outside-of-react-component)","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-03T19:33:27Z"},{"name":"Chat Hello World","note":"This lib is updated fairly frequently to keep up with Google's changes: https://www.npmjs.com/package/googlethis","tODO":"I'm sure this can be _vastly_ improved","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1143","url":"https://gist.githubusercontent.com/johnlindquist/0f0f86d0dc84f7acb30dc726da39b470/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts","title":"Script Kit AI Chat Pre-release","command":"script-kit-ai-chat-pre-release","content":"\r\nhttps://user-images.githubusercontent.com/36073/221332902-87c2d0a4-46b8-4eb1-a935-9a9fcae90e94.mp4\r\n\r\n## This is a Preview Build for Next Week's Release\r\n\r\nI'm releasing this to gather some feedback about the Chat component before I push the main release in the beginning of March. Please let me know below what you think of the new `chat` component. What do you love? What needs to change? What should be added? Please leave your ideas below! 🙏\r\n\r\nDownload here Pre-release here:\r\n\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.50.6\r\n\r\nJoin our Discord:\r\n\r\nhttps://discord.gg/qnUX4XqJQd\r\n\r\n## Chat Component Hello World\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/0f0f86d0dc84f7acb30dc726da39b470/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n\r\n## Chat Component with AI\r\n\r\n[Open openai-chat in Script Kit](https://scriptkit.com/api/new?name=openai-chat&url=https://gist.githubusercontent.com/johnlindquist/fc9f74aa387e371a59c973175201ead2/raw/e94fc92dec07dda63982bb54e756a35cfdb92532/openai-chat.ts\")\r\n\r\n```js\r\n// Name: OpenAI Chat\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { ConversationChain } = await import(\"langchain/chains\")\r\nlet { BufferMemory } = await import(\"langchain/memory\")\r\n\r\nlet llm = new OpenAI({\r\n openAIApiKey: await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n }),\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet memory = new BufferMemory()\r\nlet chain = new ConversationChain({\r\n llm,\r\n memory,\r\n})\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n await chain.call({ input })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n## Chat with a .txt File\r\n\r\n[Open doc-talk in Script Kit](https://scriptkit.com/api/new?name=doc-talk&url=https://gist.githubusercontent.com/johnlindquist/f1bfd6758815a1fb87d51a9c7893bd2e/raw/9d54e81aa9f269af0a22a44e6bfcb0f77b04f44d/doc-talk.ts\")\r\n\r\n```js\r\n/*\r\n# Doc Talk\r\n\r\nChat with a .txt file such as a book. In chat, ask questions like:\r\n- \"Who are the main characters?\"\r\n- \"Where are the key settings\"?\r\n- \"Summarize the story arc\"\r\n\r\n> Note: The chat's knowledge is limited to the loaded .txt file\r\n*/\r\n\r\n// Name: Doc Talk\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\nawait npm(\"hnswlib-node\")\r\n\r\nlet { OpenAI } = await import(\"langchain/llms\")\r\nlet { VectorDBQAChain } = await import(\"langchain/chains\")\r\nlet { HNSWLib } = await import(\"langchain/vectorstores\")\r\nlet { OpenAIEmbeddings } = await import(\r\n \"langchain/embeddings\"\r\n)\r\nlet { RecursiveCharacterTextSplitter } = await import(\r\n \"langchain/text_splitter\"\r\n)\r\n\r\nlet filePath = await path({\r\n hint: `Select a .txt file to talk to or Download Romeo and Juliet`,\r\n})\r\n\r\nif (filePath === \"__ROMEO__\") {\r\n let buffer = await download(\r\n `https://www.gutenberg.org/cache/epub/1513/pg1513.txt`\r\n )\r\n filePath = home(\"Downloads\", \"romeo-and-juliet.txt\")\r\n await writeFile(filePath, buffer)\r\n}\r\n\r\nwait(250).then(() => setLoading(true))\r\n\r\ndiv(\r\n md(`# Loading...\r\n\r\n~~~\r\nLoading in ${filePath}\r\n~~~\r\n`)\r\n)\r\n\r\nlet text = await readFile(filePath, \"utf-8\")\r\n\r\nconst model = new OpenAI({\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nconst textSplitter = new RecursiveCharacterTextSplitter({\r\n chunkSize: 1000,\r\n})\r\nconst docs = textSplitter.createDocuments([text])\r\n\r\nconst vectorStore = await HNSWLib.fromDocuments(\r\n docs,\r\n new OpenAIEmbeddings()\r\n)\r\n\r\nconst chain = VectorDBQAChain.fromLLM(model, vectorStore)\r\n\r\nsetLoading(false)\r\nlet messages = await chat({\r\n onSubmit: async query => {\r\n const res = await chain.call({\r\n input_documents: docs,\r\n query,\r\n })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Google AI\r\n\r\n\r\n[Open google-ai in Script Kit](https://scriptkit.com/api/new?name=google-ai&url=https://gist.githubusercontent.com/johnlindquist/3086fd1ddb12e16056cf116633f69547/raw/7ded0692a0909bbdd8b80eb7af69a5d2bf3d7352/google-ai.ts\")\r\n\r\n```js\r\n/*\r\n# Google AI Experiment\r\n\r\nThis is 100% experimental. I'm still learning the ins and outs of langchain.\r\nIf you have feedback on how to improve, PLEASE share 🙏\r\n\r\n\\- John Lindquist\r\n */\r\n\r\n// Name: Google AI\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\n// Note: This lib is updated fairly frequently to keep up with Google's changes: https://www.npmjs.com/package/googlethis\r\nawait npm(\"googlethis\")\r\nawait npm(\"@extractus/article-extractor\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { Tool } = await import(\"langchain/tools\")\r\nlet { initializeAgentExecutor } = await import(\r\n \"langchain/agents\"\r\n)\r\n\r\nclass GoogleThis extends Tool {\r\n name = \"search\"\r\n description =\r\n \"a search engine. useful for when you need to answer questions about current events. input should be a search query. Output should include the best result and associated URL.\"\r\n\r\n formatResults = response => {\r\n let data = response?.results\r\n ?.slice(0, 3)\r\n ?.map(r => {\r\n return `\r\ntitle: ${r.title}\r\ndescription: ${r.description}\r\nurl: ${r.url}\r\n`\r\n })\r\n .join(\"\\n\")\r\n\r\n if (response?.knowledge_panel?.title)\r\n data = `Best title: ${response?.knowledge_panel?.title}\r\n${data}`\r\n\r\n if (response?.knowledge_panel?.description)\r\n data = `Best description: ${response?.knowledge_panel?.description}\r\n${data}`\r\n\r\n return data\r\n }\r\n\r\n async call(input: string) {\r\n let google = await import(\"googlethis\")\r\n let response = await google.search(input)\r\n return this.formatResults(response)\r\n }\r\n}\r\n\r\nclass ReadURL extends Tool {\r\n name = \"read\"\r\n description = `a web scraper. Input is a url. Output is the contents of the page.`\r\n\r\n formatArticle = (url, article) => {\r\n let formatted = ``\r\n try {\r\n formatted = Object.entries(article)\r\n .filter(([key, value]) => key && value)\r\n .map(([key, value]) => {\r\n // In case the article contents are too long\r\n // TODO: Should probably wrap over to a \"Document\" paradigm here...\r\n if (typeof value === \"string\") {\r\n return [key, value?.slice(0, 1000) || \"\"]\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return [key, value.join(\",\")]\r\n }\r\n\r\n return [key, value]\r\n })\r\n .map(([key, value]) => `${key}: ${value}`)\r\n .join(\"\\n\")\r\n } catch (error) {\r\n formatted = `Couldn't read the contents of ${url}`\r\n }\r\n return formatted\r\n }\r\n\r\n async call(url: string) {\r\n let { extract } = await import(\r\n \"@extractus/article-extractor\"\r\n )\r\n let article = await extract(url)\r\n let formatted = this.formatArticle(url, article)\r\n\r\n return formatted\r\n }\r\n}\r\n\r\nlet tools = [new GoogleThis(), new ReadURL()]\r\n\r\nlet yankAnswer = async output => {\r\n return output?.generations\r\n ?.at(0)\r\n ?.at(0)\r\n ?.text.split(\"\\n\")\r\n ?.at(-1)\r\n .replace(\"Final Answer: \", \"\")\r\n}\r\n\r\nlet llm = new OpenAI({\r\n temperature: 0.7,\r\n streaming: true,\r\n callbackManager: {\r\n handleError: log,\r\n handleEnd: output => {\r\n let answer = yankAnswer(output)\r\n history = `${history}\r\nAI: ${answer}`\r\n },\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet executor = await initializeAgentExecutor(\r\n tools,\r\n llm,\r\n \"zero-shot-react-description\"\r\n)\r\n\r\n// TODO: I'm sure this can be _vastly_ improved\r\nlet history = `The AI should always include relevant URLs when possible.\r\nThe AI should use the given information and URLs to explain why it chose the answer.\r\nThe AI should avoid commas by formatting with newlines\r\n\r\n`\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n history = `${history}\r\nMe: ${input}`\r\n\r\n await executor.call({ input: history })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Read More about LangChain to Get Started with AI in Script Kit\r\n\r\nhttps://hwchase17.github.io/langchainjs/docs/overview\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-25T03:07:13Z"},{"alias":"hi","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1093","url":"","title":"Script Kit v1.48.0 - February 2023 Release","command":"script-kit-v1480-february-2023-release","content":"# Script Kit v1.48.0 - February 2023 Release\r\n\r\nDownload Script Kit v1.48.0 from: https://scriptkit.com\r\n\r\nWatch release video:\r\nhttps://www.youtube.com/watch?v=FQg8AL539_M\r\n\r\n## Drag and Drop Widgets\r\n\r\n![CleanShot 2023-02-02 at 14 31 33](https://user-images.githubusercontent.com/36073/216454341-e1aa117f-c238-4a6b-83af-075c463773ab.gif)\r\n\r\n\r\nWidgets now support `onDrop` and `onMouseDown` handlers. `onMouseDown` can be used in conjunction with `startDrag` to create drag and drop widgets as seen below.\r\n\r\nYou can put any sort of logic in the `onDrop` such as processing files using `ffmpeg`, uploading files to a server, etc, etc.\r\n\r\nThe widget `setState` can be used to show files/progress/etc in effect creating tiny application crafted _just for you_ 🤩\r\n\r\n```js\r\nlet files = []\r\n\r\nlet w = await widget(\r\n `
\r\n
Drop Files
\r\n{{file}}\r\n
\r\n`,\r\n {\r\n containerClass: `p-4 h-screen w-screen overflow-auto`,\r\n width: 300,\r\n height: 300,\r\n draggable: false,\r\n state: {\r\n files,\r\n },\r\n }\r\n)\r\n\r\nw.onDrop(event => {\r\n if (event?.dataset?.files) {\r\n files.push(...event.dataset.files)\r\n w.setState({\r\n files,\r\n })\r\n }\r\n})\r\n\r\nw.onMouseDown(event => {\r\n if (event.dataset.file) {\r\n startDrag(event.dataset.file)\r\n }\r\n})\r\n```\r\n\r\n## `// Alias` Metadata\r\n\r\nHave a script you run often? Use `// Alias` to jump that script to the top of the list when that alias is hit:\r\n\r\n```js\r\n// Alias: hi\r\nsay(\"hi\")\r\n```\r\n\r\n## Fixes for Windows/Linux Installs\r\n\r\nAfter a huge influx of users since the beginning of the year on a variety of different systems and setups, we've been able to test and fix most of the issues that have popped up around various setups with a huge focus on the installation process for Script Kit on Windows and Linux.\r\n\r\nIf you're finding that you're having issues installing Script Kit, please see this discussion: https://github.com/johnlindquist/kit/discussions/1052\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-02T21:32:51Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/970","url":"","title":"Script Kit v1.39.17 December Release","command":"script-kit-v13917-december-release","content":"# Script Kit v1.39.17 December Release\r\n\r\n\r\n\r\n\r\nDownload from https://www.scriptkit.com/\r\n\r\n## Kit.app Re-Design\r\n\r\nScript Kit now has a new look. The theming was completely re-written to help build out customs themes, so we took the opportunity to clean up the main theme.\r\n\r\nYou may also notice subtle differences in app shadows, transparency, etc which are thanks to the latest Electron updates supporting \"Panel\" windows (which we were accomplishing through third-party, less-than-ideal ways before). Speaking of updates...\r\n\r\n## Updated to the Latest Versions\r\n\r\nScript Kit is now on the latest Electron 22, node 16.17.1, and many of the internal dependencies how been updated as well. If you're a developer, you know how big of an undertaking this can be (and how refreshing it is when you're done).\r\n\r\nElectron 22 is an exciting release from a dev standpoint and we can't wait to dig into using some of the new features.\r\n\r\n## Alternate Keymap Support\r\n\r\nScript Kit shortcuts will now support your custom/international keymaps. If will also detect if a keymap has changed and remap based on the new configuration\r\n\r\n> Note: If you have a shortcut that accounted for the keymap being \"wrong\", you'll need to update it to the correct version\r\n\r\n## Run a Script From Other Apps with ~/.kit/run.txt\r\n\r\nIf you want to launch Script Kit from the terminal or another app, write the command and arguments to the `~/.kit/run.txt` file. \r\nScript Kit watches for changes and will run the command you write to the file.\r\n\r\nThe following will run the script `browse-scriptkit.js` in your ~/.kenv/scripts dir:\r\n\r\nMac:\r\n```bash\r\necho browse-scriptkit > ~/.kit/run.txt\r\n```\r\n\r\nWindows:\r\n```cmd\r\necho browse-scriptkit > %HOMEPATH%\\.kit\\run.txt\r\n```\r\n\r\n> Note: You can still use ~/.kit/kar, but I wanted to offer an alternative to our Windows friends\r\n\r\n## Watchers Menu\r\n\r\n> 🚨 Note: It's now required to manually start the snippet/clipboard watcher from the menubar icon->Watchers menu.\r\n\r\n Some users reported difficulty/freezing with the keyboard monitoring for snippets due to various other app conflicts, hardware conflicts, etc, so we decided to allow more control over starting/stopping/waking the watcher in case something happens. We worked hard in this release to address these issues, but decided it's still best to give control to the user.\r\n\r\n## Windows setSelectedText() Fixed\r\n\r\nWindows will now properly hide the prompt to be able to paste text to the app behind it\r\n\r\n## Soon... Scripts on GitHub Actions\r\n\r\nMade significant progress towards using Script Kit on GitHub Actions. Need some more time to test and handle edge cases, but we're close!!!","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-12-08T02:29:43Z"},{"enter":"Rotate Images","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/949","url":"","title":"Script Kit v1.36.0 November 2022 Release","command":"script-kit-v1360-november-2022-release","content":"# Script Kit v1.36.0 November 2022 Release\r\n\r\nDownload from [https://www.scriptkit.com/](https://www.scriptkit.com/)\r\n\r\n\r\n
\r\n \r\n
\r\n\r\n\r\n## Script Markdown\r\n\r\nPlace a multiline comment at the top of your script to display Markdown to the user when previewing the script. For example:\r\n\r\n```js\r\n/*\r\n# Rotate Images\r\n- Get the selected image from Finder\r\n- Creates 3 new versions rotated at 90, 180, and 270\r\n*/\r\n```\r\n\r\n## Script `Enter` Metadata\r\n\r\nAdding `Enter` metadata will now change the text displayed by the \"Run\" button in the script preview. For example:\r\n\r\n```js\r\n// Enter: Rotate Images\r\n```\r\n\r\n## Themes (Pro)\r\n\r\nFrom the \"Kit\" tab, you can now select a theme for the Script Kit UI. There are a variety of themes to choose from and in upcoming releases, you'll be able to create your personalized themes that you can share.\r\n\r\n## Log Window (Pro)\r\n\r\nRun a script with `alt+enter` to open the log window. This window will display the output of your script from and commands you run or any console logs.\r\n\r\n## Debugger (Pro)\r\n\r\nRun a script with `ctrl+enter` to open the debugger. The debugger will automatically pause on any `debugger` statements and allow you to step through your script and inspect/modify variables.\r\n\r\n## Account\r\n\r\nYou can now sign in to GitHub to unlock more features. The first feature is `createGist` (which is used behind the scenes in sharing scripts already) which is now exposed to users:\r\n\r\n```js\r\nlet {url} = await createGist(\"My content\")\r\n```\r\n\r\nIn the future, this account will be used for:\r\n- syncing your scripts with a GitHub repo\r\n- connecting to GitHub repos to run GitHub Actions\r\n- displaying stats about your scripts\r\n- pro plan/team plans\r\n- and much more...\r\n\r\n\r\n## Widgets Dynamic Lists\r\n\r\nWidgets can now use dynamic lists and get data from an item selected. Please check out this example and watch the youtube video for more details.\r\n\r\nhttps://github.com/johnlindquist/kit/discussions/948\r\n\r\n## Snippet Keyboard Layouts\r\n\r\nSome users reported that Snippets were not working on non-standard keyboard layouts. The snippet engine has been updated to detect your current system keyboard layout and adjust accordingly.\r\n\r\n## Focus Window\r\n> Requires \"Security & Privacy\" > \"Accessibility\" > \"Screen Recording\" permission to be enabled to work so it can get the window title names.\r\n\r\nHit colon (:) from the main menu to open the focus window script. This lists all of the windows open and allows you to select which window to bring into focus.\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-11-18T17:39:00Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/925","url":"","title":"October 2022 Release (v1.32.2) - Windows Preview 🤩, Debugger 🐞, and Repairs 🛠️","command":"october-2022-release-v1322-windows-preview-debugger-and-repairs","content":"# Script Kit 1.32.2 Released 🎉\r\n\r\nDownload here: https://scriptkit.com\r\n\r\n## Windows Preview 🤩\r\n\r\nWe are excited to announce the first Windows preview of Script Kit! This build is very close to feature-parity with the OSX version, so you can expect the vast majority of scripts you run on Mac to also run on Windows.\r\n\r\n> Note: We haven't purchased the Windows code signing certificate yet, so Windows will warn you that it can't verify Script Kit when you begin the install process. Also, this causes that Windows updates will need to be installed manually. We plan on setting up the Windows code-signing certificate by the end of the year to fix these issues then we'll remove the \"Preview\" label 😊\r\n\r\nSnippets, Watchers, Schedule, etc, etc, etc all work. If an api is not supported on Windows (mostly the functions that get information about the desktop) then it will display a \"Not supported on Windows\" message.\r\n\r\nThe \"Browse\" (`/` from the main menu) and \"File Search\" (`.` from the main menu) both required a ton of work, but they're working as well. We'll get the App Launcher sorted out in the next release.\r\n\r\n## Debugger\r\n\r\nYou can now debug your scripts by simply pressing `cmd+enter` from the main menu (`ctrl+enter` on Windows). This will open a built-in inspector that will allow you to step through your script, set breakpoints, and inspect variables.\r\n\r\nUse the `debugger` keyword to set a breakpoint in your script. The inspector will pause execution when it hits the breakpoint and you can mess around with the variables and step through the script to your heart's content. You can even invoke functions such as `setDescription()` and treat the inspector like a REPL.\r\n\r\n## Repair\r\n\r\nThe menubar now includes a `Debug` menu. From `Debug->Force Repair Kit SDK` you can force the Kit SDK to be reinstalled. This is useful if you're having issues with the Kit SDK due to npm acting up or if an update failed.\r\n\r\n## `await cutText()`\r\n\r\nThe `cutText()` function will cut the latest typed word and bring it into your script.\r\n\r\n```js\r\nlet word = await cutText()\r\n\r\n// Send word to an API, wrap the word in markdown, etc, etc\r\n```\r\n\r\n## Other Fixes\r\n\r\n* The TypeScript esbuild compiling should be faster and more stable\r\n* New install splash now shows the npm progress status\r\n* App Launcher fixes (`;` from the main menu)\r\n* Google Fixes (`~` from the main menu)","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-10-27T16:08:15Z"},{"name":"Example Postfix Snippet","snippet":"*html,,","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/901","url":"","title":"September 2022 Release (version 1.30.8) - Postfix Snippets, UI Shortcuts, VS Code Extension, and Much, much more!","command":"september-2022-release-version-1308-postfix-snippets-ui-shortcuts-vs-code-extension-and-much-much-more","content":"# Script Kit 1.30.8 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\n\r\nThis is our first release after a couple of busy summer months, and it's slam-packed full of new features and UX!\r\n\r\n## Postfix Snippets\r\n\r\nPostfix snippets are snippets that are triggered by typing a postfix after a \"variable\" of text. The variable is represented by a `*` at the beginning of snippet. In the `*html,,` example below, typing `divhtml,,` would treat `div` as the variable and render out `
Hello world
`.\r\n\r\n### Postfix Snippet Hello World\r\n\r\n```js\r\n// Name: Example Postfix Snippet\r\n// Snippet: *html,,\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"I'm the '*' content\")\r\n\r\nsetSelectedText(`<${value}>Hello world${value}>`)\r\n```\r\n\r\n### Postfix Snippet Query API\r\n\r\nThe snippet can also take the content of the `*` and post it to an API for more complex scripts.\r\n\r\nIn this example, the content is used to query google and create a markdown link from the word you typed.\r\n\r\n```js\r\n// Name: Markdown Link from Google Snippet\r\n// Snippet: *,.\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet google = await npm(\"googlethis\")\r\n\r\nconst options = {\r\n page: 0, \r\n safe: false,\r\n additional_params: { \r\n hl: 'en' \r\n }\r\n}\r\n\r\nlet value = await arg(\"I'm the asterisk content\")\r\nlet response = await google.search(value, options);\r\n\r\nlet url = response.results[0].url\r\n\r\nsetSelectedText(`[${value}](${url})`)\r\n```\r\n\r\n\r\n## `template()`\r\n\r\nThe `template` prompt will present the editor populated by your template. You can then tab through each variable in your template and edit it. \r\n\r\nTemplates pair really, _really_ nicely with Snippets!\r\n\r\n### Template Hello World\r\n\r\n```js\r\nlet text = await template(`Hello $1!`)\r\n```\r\n\r\n### Standard Usage\r\n\r\n```js\r\nlet text = await template(`\r\nDear \\${1:name},\r\n\r\nPlease meet me at \\${2:address}\r\n\r\n Sincerely, John`)\r\n```\r\n\r\n\r\n## `await docs()`\r\n\r\n`docs()` takes the file path of a markdown file and displays it as a list of choices.\r\n\r\nEach h2 is displayed as a choice while the content of the h2 is displayed in the preview.\r\n\r\nSelected the choice will return current h2.\r\n\r\n### Submitting a `docs()` value\r\n\r\nIf you'd rather submit a value instead of the h2, then use an HTML comment to specify the value under the h2's content:\r\n\r\n```md\r\n## I'm the Choice Header\r\n\r\n```\r\n\r\n### `docs()` Example\r\n\r\n```js\r\n// Name: Example Docs\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await download(`https://raw.githubusercontent.com/BuilderIO/qwik/main/README.md`)\r\n\r\nlet filePath = tmpPath(\"README.md\")\r\nawait writeFile(filePath, buffer)\r\n\r\n\r\nlet selectedDoc = await docs(filePath)\r\n\r\ndev({selectedDoc})\r\n```\r\n\r\n## UI Shortcuts in the \"Action Bar\"\r\n\r\nThe September 2022 release adds a new \"Action Bar\" at the bottom of the UI which supports custom shortcuts.\r\n\r\nA shortcut has a `name`, `key`, `onPress` and `bar` property. When you press the shortcut, it will trigger the `onPress` function. You can also click on the shortcut to trigger the `onPress` function.\r\n\r\n```js\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n```\r\n\r\n\r\nAdd each shortcuts to a `shortcuts` array which is passed to the prompt config (most commonly, the first argument of `arg()`).\r\n\r\n### UI Shortcuts Example\r\n\r\n```js\r\n// Name: Shortcuts Example\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n\r\nlet reloadChoices = {\r\n name: \"Reload\",\r\n key: \"cmd+l\",\r\n bar: \"right\",\r\n onPress: async () => { \r\n setChoices([\"one\",\"two\",\"three\"])\r\n }\r\n} \r\n\r\nlet submitInputNotChoice = {\r\n name: \"Submit Input\",\r\n key: \"cmd+i\",\r\n bar: \"right\",\r\n onPress: async (input) => { \r\n submit(input)\r\n }\r\n} \r\n\r\nlet runAppLauncher = {\r\n name: \"Launch App\",\r\n key: \"cmd+a\",\r\n bar: \"left\",\r\n onPress: async () => { \r\n await run(kitPath(\"main\", \"app-launcher.js\"))\r\n }\r\n}\r\n\r\nlet result = await arg({\r\n placeholder: \"Shortcut demo\",\r\n shortcuts: [ \r\n clearInput,\r\n reloadChoices,\r\n submitInputNotChoice,\r\n runAppLauncher\r\n ]\r\n}, [\"apple\", \"banana\", \"cherry\"])\r\n\r\nawait div(md(`## ${result}`))\r\n```\r\n\r\n## Recent Script Moved to Top\r\n\r\nThe most recently run script is now moved to the top of the list so that the next time you open the main prompt, you can quickly run it again.\r\n\r\n\r\n## VS Code Extension\r\n\r\nWe now have a VS Code extension which allows you to run scripts directly from VS Code:\r\n\r\nhttps://marketplace.visualstudio.com/items?itemName=johnlindquist.kit-extension\r\n\r\nMore features are coming soon!\r\n\r\n## Menubar Updates\r\n\r\nThe menubar menu now has a \"Dev Tools\" menu which allows you to perform some common commands that might not be obvious from the main UI.\r\n\r\nThe menu also lists all of the running processes so that you can easily terminate them without having to hunt around from process ids.\r\n\r\n## cmd+tab to Widgets and `dev`\r\n\r\nWhen the main prompt is open, a widget is open, or a `dev` window is open, the Script Kit icon will be added to the doc to allow you to cmd+tab back to widgets/editor/dev/etc. Since Script Kit is a combination of a temporary prompt (like Alfred) but also can host long-running widgets, we had to work through various scenarios of when cmd+tab can be available. Hopefully we've landed on a solution that works for everyone.\r\n\r\n## Main Menu `API` and `Guide`\r\n\r\nThe Main Menu of Script Kit now hosts `API` and `Guide` tabs which allow you to easily copy code snippets or create new scripts from the examples. They're also both easy to update, so you can expect more samples and explanations to play with in the future!\r\n\r\n## `await emoji()`\r\n\r\nA brand new emoji picker\r\n\r\n```js\r\nlet e = await emoji()\r\nsetSelectedText(e.emoji)\r\n```\r\n\r\n## `await fields()`\r\n\r\n```js\r\nlet [first, last] = await fields([\"First name\", \"Last name\"])\r\n\r\ndev({\r\n first,\r\n last\r\n})\r\n```\r\n\r\n## `beep()`\r\n\r\nAnd the most exciting announcement of all, `beep()` is now available!\r\n\r\n`beep()` plays a beep sound.\r\n\r\n### `beep()` Example\r\n\r\n```js\r\nbeep()\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-09-29T08:49:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/787","url":"","title":"June 2022 Release (version 1.19.3) - Menu, Native Keyboard, ignoreBlur","command":"june-2022-release-version-1193-menu-native-keyboard-ignoreblur","content":"# Script Kit 1.19.3 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\nDirect downloads:\r\n* Intel: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3.dmg\r\n* m1: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3-arm64.dmg\r\n\r\n## Features\r\n\r\n* Moved all notifications to menu colored “dots” (red, orange, green)\r\n * In the future, the user will be able to set these notifications as well\r\n* Completely re-vamped menu for common options\r\n* Implemented an “auth helpers” when user does actions that require “accessibility permissions” (Snippets, clipboard history, setSelectedText, etc)\r\n* `ignoreBlur` allows window to go behind other windows and stay open\r\n* `node` is now stored in `~/.knode` (instead of ~/.kit/node) to allow npx to work in the terminal\r\n* `node` version set to v16.14.2. Version is now synced with Kit.app which resolves conflicts with native packages in scripts\r\n* Keyboard actions (copy/paste/type) have moved from applescript to native code. Snippets, setSelectedText, etc should now feel as “instant” as possible.\r\n* You can now `await hide()` for when you need to make sure the prompt is hidden before continuing the script. This was necessary since the new keyboard actions were so fast.\r\n* Moved the script sharing auth flow to a widget\r\n* Internal: Can now set the state of Kit.app from a script to help with debugging\r\n\r\n\r\n## Fixes\r\n\r\n* App launcher failed to parse malformed App plist icons\r\n* Editor whitespace collapsing on HiDPI screens\r\n* Touchbar key while prompt open would cause crash\r\n* Bin files sometimes didn’t regenerate properly when re-launching the app\r\n* Updater issues\r\n* Background UI not updating when user manually terminates process\r\n* Performance: Moved the file watcher to a spawned process that sends events to the App\r\n\r\n## More to come...\r\n\r\nFinally moved into the new house and settled in. Personal life was too hectic to stream much or do release notes on past couple releases. Expect more streaming, sharing scripts, promotion, and news from me. Cheers! 🥂\r\n\r\n_Here's a preview of the new menu and an example of a dot notification_\r\n\r\n\r\n![The new dot notifications and re-vamped menu](https://cdn.discordapp.com/attachments/963905444823318578/988844882665803926/unknown.png)\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-06-21T16:44:14Z"},{"name":"Widget Hello World","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/745","url":"https://gist.githubusercontent.com/johnlindquist/ca174899643e86f416d301d9599bb4e8/raw/55d334c6dc412c0346a750348d8c0ffa2b8650ba/widget-hello-world.ts","title":"March 2022 Release (version 1.7.3) - Widgets! Built-in Terminal, Menu, and Built-in Editor Types","command":"march-2022-release-version-173-widgets-built-in-terminal-menu-and-built-in-editor-types","content":"# March 2022 Release (version 1.7.3)\r\n\r\nScript Kit should auto-update or you can grab the downloads here: https://www.scriptkit.com/\r\n\r\n## Widgets - `await widget()`\r\n\r\nA widget is a detached UI window that can control and listen to a script.\r\n\r\n\r\n\r\n[Open widget-hello-world in Script Kit](https://scriptkit.com/api/new?name=widget-hello-world&url=https://gist.githubusercontent.com/johnlindquist/ca174899643e86f416d301d9599bb4e8/raw/55d334c6dc412c0346a750348d8c0ffa2b8650ba/widget-hello-world.ts\")\r\n\r\n```js\r\n// Name: Widget Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet message = await arg(\"Hello what?\")\r\nawait widget(`
Hello ${message}
`)\r\n\r\n```\r\n\r\n### Widget Events\r\n\r\n\r\n[Open widget-events in Script Kit](https://scriptkit.com/api/new?name=widget-events&url=https://gist.githubusercontent.com/johnlindquist/1ce2972fdeed0773450f4dba3f3f2c00/raw/6834ccde194ea471f403df9366a7ac283cb853bb/widget-events.ts\")\r\n\r\n```js\r\n// Name: Widget Events\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = \"\"\r\nlet count = 0\r\n\r\nlet w = await widget(`\r\n
\r\n`)\r\n})\r\n\r\nw.onInput((event) => {\r\n text = event.value\r\n})\r\n\r\nw.onMoved(({ x, y}) => {\r\n // e.g., save position\r\n})\r\n\r\nw.onResized(({ width, height }) => {\r\n // e.g., save size\r\n})\r\n```\r\n\r\n## Closing a Widget\r\nThere are 3 ways to close a widget:\r\n1. Hit \"escape\" with the widget focused\r\n2. End the process of the widget. Hit cmd+p with the main menu focused to see the running processes or `exit()` anywhere in the script.\r\n3. Use a `ttl` (time to live) in the options when creating a widget\r\n\r\n## \"Always on Top\" and Locking the Widget\r\n\r\n[Open widget-always-on-top in Script Kit](https://scriptkit.com/api/new?name=widget-always-on-top&url=https://gist.githubusercontent.com/johnlindquist/bfd8ec67d9632867b0faf4e808381948/raw/90f766f21af8c88760409215e569baef9d8f0238/widget-always-on-top.ts\")\r\n\r\n```js\r\n// Name: Widget Always on Top\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait widget(`
🇺🇦
`, {\r\n alwaysOnTop: true,\r\n transparent: true\r\n})\r\n\r\n```\r\n\r\nWith a widget focused, press cmd+l to \"Lock\" the widget. This will disable any possible mouse interactions (including moving, resizing, etc) and allow you to click through the widget to any windows underneath.\r\n\r\nTo \"Unlock\":\r\n1. three-fingered swipe up on OSX\r\n2. focus the widget\r\n3. hit cmd+l\r\n\r\nYou can now hit move, escape, etc the widget.\r\n\r\n## Built-in Terminal - `await term()`\r\n\r\n`term` is Script Kit's built-in terminal.\r\n\r\n### From the Main Menu\r\n\r\nType > into the main menu to open `term`\r\n\r\n### From a Script\r\n\r\nUse the `await term()` API to switch to the terminal.\r\n\r\n[Open term-hello-world in Script Kit](https://scriptkit.com/api/new?name=term-hello-world&url=https://gist.githubusercontent.com/johnlindquist/10420bab68da357b572c1e703c2c5a43/raw/a287e443f1e57d4541f50feba28b86e1702ff515/term-hello-world.ts\")\r\n\r\n```js\r\n// Name: Term Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait term(`echo 'Hello World!'`)\r\n```\r\n\r\n> Note: If you want spawn a new Mac terminal, use `await terminal()`\r\n\r\n### Pass Terminal Output to Script\r\n\r\nIf you end the terminal with cmd+enter, the script will continue and you can grab the latest text output from the terminal.\r\n\r\n> 🐞: ctrl+any key will also end the terminal. This is a bug (it was only meant to be ctrl+c) which I'll fix soon. I'm also open to ideas for other shortcuts to \"end\" a terminal that aren't taken by vim/emacs/etc, because I know I'll be missing some.\r\n> 🐞: `term` doesn't grab keyboard focus when opening. I'll get that fixed ASAP!\r\n\r\n[Open term-returns in Script Kit](https://scriptkit.com/api/new?name=term-returns&url=https://gist.githubusercontent.com/johnlindquist/935445caef26d1c13f195533569cd0cc/raw/ca921b09480a3d7b5bf63e7a777011199e642fb9/term-returns.ts\")\r\n\r\n```js\r\n// Name: Term Returns\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = await term(`ls ~/.kit`)\r\nawait editor(text)\r\n```\r\n\r\n## Menubar - `menu()`\r\n\r\n`menu` allows you to customize the menubar for Script Kit.\r\n\r\n\r\n\r\n[Open menu-hello-world in Script Kit](https://scriptkit.com/api/new?name=menu-hello-world&url=https://gist.githubusercontent.com/johnlindquist/6aeb6a3f916bfc40e9acd6b9d4388b34/raw/21d8c70da05f08e454d81cb0ccebbbbc82b42f7d/menu-hello-world.ts\")\r\n\r\n```js\r\n// Name: Menu Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nmenu(`Hello 🌎`)\r\n```\r\n\r\n### Menu with Scripts\r\n\r\nThe second arg of menu can be an array of scripts you wish to present in a drop-down menu. This way, on left-click, you'll get a list of scripts to pick from from the menubar rather than opening the main Script Kit UI.\r\n\r\n[Open menu-with-scripts in Script Kit](https://scriptkit.com/api/new?name=menu-with-scripts&url=https://gist.githubusercontent.com/johnlindquist/0e07e0a8bd4926be6d843ce49fbb4474/raw/4a111ceeae7725525fdcf8bf546105698a4ac4c9/menu-with-scripts.ts\")\r\n\r\n```js\r\n// Name: Menu with Scripts\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n// An empty string means \"Use default Script Kit icon\"\r\nmenu(``, [\r\n \"app-launcher\"\r\n])\r\n```\r\n\r\n## Built-in Editor Types\r\n\r\nScript Kit's built-in editor now loads all of Script Kit's types! This was a huge undertaking that everyone just expects to work. You all know how that feels 😇\r\n\r\n> 🐞: Please let me know if you see any missing. I noticed that I missed the types for `terminal` and `iterm` when putting this post together 🤦♂️.\r\n\r\n## March Plans\r\n\r\nI'm dedicating March to DOCUMENTATION!!! (and bug-fixes)... I have _a lot_ of script requests to follow-up on and work around the newsletter and other non-app stuff. I'm also moving this month, so y'all know how stressful that can be. So expect the April build to be extremely light feature-wise, but I will be set up in the new house ready to much more live-streaming and communication. Can't wait to share more! 🙂\r\n\r\n## Questions?\r\n\r\nI'm happy to help with any questions you may have!","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-03-01T19:45:06Z"},{"name":"Menubar Demo","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/705","url":"https://gist.githubusercontent.com/johnlindquist/ef01308eb63715970f26ee1378473194/raw/9ad4219968d9ec1a9747811a71c678ad8e241ec0/menubar-demo.ts","title":"February 2021 (version 1.6.9) - Built-in Editor, New Main Menu Features, `await path()`, Event handlers, Beta pro features, Terminate Process","command":"february-2021-version-169-built-in-editor-new-main-menu-features-await-path-event-handlers-beta-pro-features-terminate-process","content":"Been a _busy_ month of major Script Kit features!\r\n\r\n## Built-in Editor\r\n\r\nScript Kit's number one goal is to make writing time-saving scripts easier. So now Script KIt comes with a pre-configured editor, complete with autocompletion and error checking:\r\n\r\nhttps://user-images.githubusercontent.com/36073/152349718-9f9af13f-4cbb-4444-810a-2f1281938106.mp4\r\n\r\nIf you're already using vs code, you can switch to the \"Kit\" editor in the `Kit` tab -> `Change Editor`.\r\n\r\n## Main Menu Shortcuts and `/`, `~`, and `>`\r\n\r\nThe Script Kit main menu will continue to grow in features. \r\n\r\n### Shortcuts\r\n\r\n* `cmd+f` - Does a `grep` search through all of your scripts\r\n* `cmd+p` - Launches a `Processes` menu for currently running scripts\r\n\r\n### Path Mode\r\nType the following characters to change the mode of the main menu:\r\n* ~ Switches to a path selector mode in your home directory\r\n* / Switches to a path selector mode in your root directory\r\n\r\nNavigate with right/left or tab/shift+tab then select with return. Here's an example of typing `~`\r\n\r\nhttps://user-images.githubusercontent.com/36073/152386816-8be054d6-047c-416f-ae2b-dce1723d222c.mp4\r\n\r\n### Command Mode\r\n\r\n* > Switches to a command mode to execute a command\r\n\r\n\r\nhttps://user-images.githubusercontent.com/36073/152387376-b2e8b71a-4980-4d23-8c98-4f56f3ce1fdd.mp4\r\n\r\n\r\n### Future Work\r\nIn the March release, planning on these:\r\n\r\n* , List system preferences\r\n* . App launcher\r\n* ; MEGA MENU WITH EVERYTHING 🤭\r\n\r\n## `await path()`\r\n\r\nYou can now prompt to select a path. This UI works exactly like \"path mode\" above.\r\n\r\n```js\r\nlet selectedPath = await path()\r\ncd(selectedPath)\r\n\r\nawait exec(`git pull`) // this will now operate based on the selectedPath\r\n```\r\n\r\n## Event Handlers\r\n\r\nWhen building the `path` prompt, I realized it just wasn't possible to do in a script. So I put in the effort to expose the event handlers from the app into the prompt. So even though `path` behaves very differently, it's still an `arg` with customized handlers. You can override many of the handlers yourself for customized prompts:\r\n\r\nFor example, you can override the default behavior of `Escape` terminating your current script:\r\n\r\n```js\r\n// Submit the current input when you hit escape\r\nawait arg({\r\n onEscape: (input)=> {\r\n submit(input)\r\n }\r\n})\r\n```\r\n\r\nOverriding handlers is definitely considered \"advanced\", so I'm happy to answer any questions!\r\n\r\nHere's a list of all the new `arg` config properties:\r\n```js\r\nexport interface ChannelHandler {\r\n (input: string, state: AppState): void | Promise\r\n}\r\n\r\nexport interface PromptConfig\r\n onNoChoices?: ChannelHandler\r\n onChoices?: ChannelHandler\r\n onEscape?: ChannelHandler\r\n onAbandon?: ChannelHandler\r\n onBack?: ChannelHandler\r\n onForward?: ChannelHandler\r\n onUp?: ChannelHandler\r\n onDown?: ChannelHandler\r\n onLeft?: ChannelHandler\r\n onRight?: ChannelHandler\r\n onTab?: ChannelHandler\r\n onInput?: ChannelHandler\r\n onBlur?: ChannelHandler\r\n onChoiceFocus?: ChannelHandler\r\n\r\n debounceInput?: number\r\n debounceChoiceFocus?: number\r\n\r\n onInputSubmit?: {\r\n [key: string]: any\r\n }\r\n onShortcutSubmit?: {\r\n [key: string]: any\r\n }\r\n}\r\n```\r\n\r\n## onInputSubmit, onShortcutSubmit\r\n\r\nIf you want to create \"shortcuts\" to submit specific values, can use the new `onInputSubmit` and `onShortcutSubmit`. These allow you to bind text or shortcuts to submit values. This is exactly how the main menu works:\r\n\r\n![CleanShot 2022-02-03 at 09 47 42](https://user-images.githubusercontent.com/36073/152388672-db242f4e-20e2-4645-a326-a8bbc960f63d.png)\r\n\r\n\r\n## Beta Pro Features: Menubar\r\n\r\nYou can now customize the text of the Script Kit menubar icon to say anything with the `pro.beta.menubar` method. In the future, you'll be able to build out an entire menu, but I thought I'd sneak this feature in for fun in this build:\r\n\r\n[Open menubar-demo in Script Kit](https://scriptkit.com/api/new?name=menubar-demo&url=https://gist.githubusercontent.com/johnlindquist/ef01308eb63715970f26ee1378473194/raw/9ad4219968d9ec1a9747811a71c678ad8e241ec0/menubar-demo.ts\")\r\n\r\n```js\r\n// Name: Menubar Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"Set the menubar to:\")\r\npro.beta.menubar(value)\r\n\r\n\r\n```\r\n\r\nhttps://user-images.githubusercontent.com/36073/152394319-d9e071fe-edcd-4cf2-be3e-60d5ba7b01cd.mp4\r\n\r\n\r\n## Terminate Processes\r\n\r\nIf you need to end a script that's running in the background, stuck on an exec command, or whatever reason, open the main menu with the cmd+; shortcut, then press this button (or hit cmd+p. This will open a \"terminate processes\" window where you can end your scripts:\r\n\r\n![CleanShot 2022-02-03 at 10 20 45@2x](https://user-images.githubusercontent.com/36073/152394881-cf612921-1f00-4458-861a-3538053377dd.png)","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-01-30T00:36:03Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/669","url":"","title":"Script Kit for Linux - Developer Preview","command":"script-kit-for-linux-developer-preview","content":"# Script Kit for Linux - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. Building from the \"Mac\" source\r\n\r\nCurrently, the Linux build builds from the exact same branch as the Mac build. While this works fine, for now, we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out details next year.\r\n\r\n2. The Linux build is missing all the OS-specific tools\r\n\r\nLinux currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n3. I've Only Tested on Ubuntu through a Parallels vm\r\n\r\nObviously will need some more real-world testing.\r\n\r\n## Where to Download\r\n\r\nDownload the AppImage here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-12-24T08:15:12Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/655","url":"","title":"🥳 Script Kit Launch Day 🎉","command":"script-kit-launch-day","content":"# Script Kit is Officially Released! 🎉\r\n\r\nDownload now from https://scriptkit.com\r\n\r\n## Free Script Kit Course on [egghead.io](https://egghead.io)\r\n\r\nTo help you get started, I put together a short course as a feature tour:\r\n\r\nhttps://egghead.io/courses/script-kit-showcase-for-optimizing-your-everyday-workflows-e20ceab4\r\n\r\nIf you've been using Script Kit for a while on the beta, you know it can do much, much more than what's shown in the lessons, but everyone has to start somewhere. Speaking of the beta...\r\n\r\n## Beta Channel Discontinued\r\n\r\nIf you installed the beta, please download from https://scriptkit.com, quit Kit.app, and replace with the new version. This will put you on the “Main” channel. Updates will be ~monthly. The beta channel is discontinued ❗️\r\n\r\nAlso, thank you so, so much for all your feedback and patience with updates over the past year. You’ve really helped make Script Kit what it is today and I’m forever grateful 🙏\r\n\r\n## Windows Developer Preview\r\n\r\nThe details for the Windows build are found here: https://github.com/johnlindquist/kit/discussions/654\r\n\r\n## Plans for 2022\r\n\r\n1. Make the dev setup more contribution-friendly. I would love to accept PRs later next year.\r\n2. Get the Windows build to parity with Mac.\r\n3. Lots of lessons and scripts. I can finally spend more time sharing scripts than working on the app 😎\r\n4. Research into Rust, runtimes, and utilities that can provide any benefit to making our scripts better.\r\n5. Focus on \"export to serverless function\", \"export as github action\", and other ways to maximize the work you put into your scripts.\r\n5. Script Kit Pro. A paid version with additional features not found in the base version. Not ready to talk about it, but it's exciting!\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-12-17T18:33:08Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/654","url":"","title":"Script Kit for Windows - Developer Preview","command":"script-kit-for-windows-developer-preview","content":"# Script Kit for Windows - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. I haven't bought a certificate to add to the build:\r\n- You'll see many \"untrusted\" warnings when downloading/installing\r\n- Auto-updating will not work\r\n\r\n2. I haven't decided if the Windows repo will be a fork, branch, or main\r\n\r\nCurrently, the Windows build builds from the exact same branch as the Mac build. While this works fine, for now, I'm pretty sure we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out the details next year.\r\n\r\n3. The Windows build is missing all the OS-specific tools\r\n\r\nWindows currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n4. I've Only Tested It on Two Laptops\r\n\r\nThe Mac version has been used/tested by many, many people. I have two Windows laptops at home to test it on. It works well, but I don't know how much your mileage will vary.\r\n\r\n## Where to Download\r\n\r\nDownload the installer here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1\r\n\r\nAgain, this build will not auto-update. I'll post announcements here when new versions are available and you'll have to download the new version each time until I have the certificate and release servers worked out. Honestly, I'll probably write a \"check for Windows update and download\" script then you can just run that on a `// Schedule: 0 8 * * *` 😉","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-12-17T15:06:29Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/615","url":"","title":"beta.114 - Info, Settings, Choice Events 🎛","command":"beta114-info-settings-choice-events","content":"# beta.114 - Info, Settings, Choice Events\r\n\r\n## Displaying Temporary Info\r\n\r\nUntil now, `await div()` worked by waiting for the user to hit enter/escape. This still works fine, but if you want to \"timeout\" a `div` to display temporary info without user input, this entire script will run without any user interaction:\r\n\r\n[Install non-blocking-div](https://scriptkit.com/api/new?name=non-blocking-div&url=https://gist.githubusercontent.com/johnlindquist/87e92156251d09a02154f04772f1e9bf/raw/be6dde40a7f5e1f3b8eaa9abf68d8698031cd3de/non-blocking-div.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet classes = `p-5 text-3xl flex justify-center items-center text-center`\r\n\r\ndiv(`Wait 1 second...`, classes)\r\nawait wait(1000)\r\n\r\ndiv(`Just 2 more seconds...`, classes)\r\nawait wait(2000)\r\n\r\ndiv(`Almost there...`, classes)\r\nawait wait(3000)\r\n\r\n```\r\n\r\n## Remember Selection\r\n\r\nI needed to build a settings \"panel\", so I wanted to make a list that could toggle. \r\n\r\n\r\n![CleanShot 2021-11-22 at 12 08 29](https://user-images.githubusercontent.com/36073/142920816-3bf47911-578b-4e2f-9662-10257287fde4.png)\r\n\r\nThe solution was to remember the previous choice by `id`. Any time `arg` is invoked, it will check to see if a choice has an id that matched the previously submitted choice and focus back on it. This enables you to hit enter repeatedly to toggle a choice on and off.\r\n\r\n[Install remember-selection](https://scriptkit.com/api/new?name=remember-selection&url=https://gist.githubusercontent.com/johnlindquist/a86395d809c260d943f9763023f5a6f0/raw/4c1057b8500fcdf34fcd179af52f09cc7dee9ca4/remember-selection.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Off\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n]\r\n\r\nlet argConfig = {\r\n placeholder: \"Toggle items\",\r\n flags: {\r\n end: {\r\n shortcut: \"cmd+enter\",\r\n },\r\n },\r\n}\r\n\r\nwhile (true) {\r\n let item = await arg(argConfig, data)\r\n data.find(i => i.id === item.id).name =\r\n item.name === \"On\" ? \"Off\" : \"On\"\r\n\r\n if (flag.end) break\r\n}\r\n\r\nawait div(JSON.stringify(data), \"p-2 text-sm\")\r\n```\r\n\r\nYou could also use this when making a sequence of selections:\r\n\r\n[Install remember-sequence](https://scriptkit.com/api/new?name=remember-sequence&url=https://gist.githubusercontent.com/johnlindquist/80f9d005e5bff92691125f736199aa2c/raw/4e05c118bc91defe5e2f39cff20eb9862f4c6a2d/remember-sequence.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"One\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Two\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Three\",\r\n },\r\n]\r\n\r\nlet selections = []\r\n\r\nlet one = await arg(`First selection`, data)\r\nselections.push(one)\r\n\r\nlet two = await arg(\r\n {\r\n placeholder: `Second selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(two)\r\n\r\nlet three = await arg(\r\n {\r\n placeholder: `Third selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(three)\r\n\r\nawait div(\r\n selections.map(s => s.name).join(\", \"),\r\n \"p-2 text-sm\"\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n[Install no-choices-event](https://scriptkit.com/api/new?name=no-choices-event&url=https://gist.githubusercontent.com/johnlindquist/5534589a322bbb384e5bf4dbcbf00864/raw/1a7c2500149db3b8731e900646d568fa7fb5ed74/no-choices-event.js\")\r\n\r\n## Choice Events\r\n\r\n`onNoChoices` and `onChoices` allows Kit.app to tell your script when the user has typed something that filtered out every choice. Most commonly, you'll want to provide a `setHint` (I almost made it a default), but you can add any logic you want.\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\r\n {\r\n placeholder: `Pick a fruit`,\r\n onChoices: async () => {\r\n setHint(``)\r\n },\r\n onNoChoices: async input => {\r\n setHint(`No choices matched ${input}`)\r\n },\r\n },\r\n [`Apple`, `Orange`, `Banana`]\r\n)\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-22T19:09:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/587","url":"","title":"beta.98 - Previews 👀, Docs, devTools, updater improvements","command":"beta98-previews-docs-devtools-updater-improvements","content":"## Previews\r\n\r\nCreating the previews feature was a huge undertaking, but it really paid off. You can now render html into a side pane by simply providing a `preview` function. A preview can be a simple string all the way to an async function per choice that loads data based on the currently selected choice. For examples, see here #555 \r\n\r\n> You can toggle previews on/off with cmd+p\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507471-db3e4454-f0ef-4e6b-891b-bd4344a40e85.mp4\r\n\r\n## Docs\r\n\r\nAlong with previews comes the built-in docs.\r\n\r\n- Docs are built from the GitHub discussions [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) category\r\n- Each time I post/update a doc, a webhook builds the docs into a json file, Kit.app checks for a new docs.json once a day (or you can manually update them from the `Help->Download Latest Docs`\r\n- You can _click an example to install it!_ 🎉\r\n- I'll keep working on docs and examples. Please ask any questions over in the [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) section if you'd like to see something clarified.\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507953-02d44174-3ac0-43d7-8d92-4319e917d512.mp4\r\n\r\n## Dev Tools\r\n\r\nPass any data into `devTools` to pop open a Dev Tools pane so you can interact with the data. `devTools` will first log out the data, but it's also assigned to an `x` variable you can interact with in the console.\r\n\r\n> `devTools` will be another paid feature once Script Kit 1.0 releases\r\n\r\nhttps://user-images.githubusercontent.com/36073/141508954-df3ea997-a49e-4fdd-bd40-7bff76024a6d.mp4\r\n\r\n## Updater Fixes\r\n\r\nA few users reported a strange behavior with the updater. If you've had any issues with it, please download a fresh copy of Kit.app from https://scriptkit.com and overwrite the old version. There are many more guards around the updating logic to prevent those issues from cropping up again.\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T17:32:40Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/488","url":"","title":"Script Kit online on Stackblitz ⚡️","command":"script-kit-online-on-stackblitz","content":"I spent last week getting Script Kit running \"in browser\" to emulate the terminal experience over on Stackblitz. Here's a quick demo:\r\n\r\nhttps://stackblitz.com/edit/node-rnrhra?file=scripts%2Frepos-to-markdown.js\r\n\r\nThe plan is to use this to host interactive demos for the guide/docs. I'd appreciate if you could play around with it a bit and see if I missed anything.\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-10-18T20:06:28Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/457","url":"","title":"TypeScript support! 🚀","command":"typescript-support","content":"beta.62 brings with it a long-awaited, much-requested feature: TypeScript support!\r\n\r\n![CleanShot 2021-09-27 at 10 42 38](https://user-images.githubusercontent.com/36073/134951810-31754840-85c3-4ad3-a493-59c757fdda07.png)\r\n\r\n## TypeScript Support 🚀\r\n\r\n### 1. But, how?\r\n\r\nEach time your run a TS script, Script Kit will compile the TS script using `esbuild` to a JS script in a `.scripts` dir (notice the \"dot\"). The compiled JS script is then imported from there. Using `.scripts` as a sibling dir will help avoid any `import`/path issues. You can also write TS \"library\" files in your `~/.kenv/lib` dir and import them into your script just fine.\r\n\r\nIf you're experienced with `esbuild` and curious about the settings, they look like this:\r\n\r\n```js\r\nlet { build } = await import(\"esbuild\")\r\n\r\nawait build({\r\n entryPoints: [scriptPath],\r\n outfile,\r\n bundle: true,\r\n platform: \"node\",\r\n format: \"esm\",\r\n external: [\"@johnlindquist/kit\"],\r\n})\r\n```\r\n\r\nThis also opens the door to exporting/building/bundling scripts and libs as individual shippable tools which I'll investigate more in the future.\r\n\r\n### 2. Can I still run my JS scripts if I switch to TS?\r\n\r\nYes! Both your TS and JS scripts will show up in the UI.\r\n\r\n### 3. Why the `import \"@johnlindquist/kit\"`?\r\n\r\nWhen you create a new TS script, the generated script will start with the line: `import \"@johnlindquist/kit\"`\r\n\r\nThis is mostly to make your editor stop complaining by forcing it to load the type definition files and forcing it to treat the file as an \"es module\" so support \"top-level `await`\". It's not technically required since it's not technically importing anything, but your editor will certainly complain very loudly if you leave it out.\r\n\r\n### 4. Where is the setting stored?\r\n\r\nLook in your `~/.kenv/.env` for `KIT_MODE=ts`.\r\n\r\n## fs-extra's added to global\r\n\r\nThe [fs-extra methods](https://www.npmjs.com/package/fs-extra#methods) are now added on the global space. I found myself using `outputFile`, `write/readJson`, etc too often and found them to be a great addition. The only one missing is `copy` since we're already using that to \"copy to clipboard\". You can bring it in with the normal import/alias process if needed, e.g., `let {copy:fsCopy} = await import(\"fs-extra\")`\r\n\r\n## Sync Path\r\n\r\n![CleanShot 2021-09-27 at 11 10 26](https://user-images.githubusercontent.com/36073/134954703-7c9d779f-268a-4f8b-973a-59ac71eebaf0.png)\r\n\r\nYou may notice running scripts from the Script Kit app that some commands you can run in your terminal might be missing, like \"yarn\", etc.\r\n\r\nRun the following command in your terminal to copy the $PATH var from your terminal to your `~/.kenv/.env`. This will help \"sync\" up which commands are available between your terminal and running scripts from the app.\r\n\r\n```bash\r\n~/.kit/bin/kit sync-path\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-09-27T17:25:03Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/442","url":"","title":"Scripts in GitHub actions (preview)","command":"scripts-in-github-actions-preview","content":"## tl;dr Here's an example repo\r\n\r\nThe example script creates a release, downloads an image, and uploads it to the release.\r\n\r\nhttps://github.com/johnlindquist/kit-action-example\r\n\r\n## Template Repo\r\n\r\nThis page has a \"one-click\" clone so you can add/play with your own script.\r\n\r\nhttps://github.com/johnlindquist/kit-action-template\r\n\r\n## What is it?\r\n\r\nUse any of your scripts in a GitHub action. `use` the `kit-action` and point it to a scripts in your `scripts` dir:\r\n\r\n```yml\r\nname: \"example\"\r\non:\r\n workflow_dispatch:\r\n pull_request:\r\n push:\r\n branches:\r\n - main\r\n\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\" # The name of a script in your ./scripts dir\r\n```\r\n\r\n## Add env vars:\r\n\r\nYou most likely add [\"secrets\" to GitHub actions](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-an-environment), so you'll want to pass them to your scripts as environment variables:\r\n\r\n```yml\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\"\r\n env:\r\n REPO_TOKEN: \"${{ secrets.REPO_TOKEN }}\" # load in your script with await env(\"REPO_TOKEN\")\r\n```\r\n\r\n## Works with your existing repos\r\n\r\nFeel free to add this action and a `scripts` dir to your existing repos. It automatically loads in your repo so you can parse `package.json`, compress assets, or whatever it is you're looking to add to your CI.\r\n\r\n## What does \"preview\" mean?\r\n\r\nEverything is working, but it's pointing to the \"main\" branch rather than a tagged version. Once I get some feedback, I'll tag a \"1.0\" version so you can `uses: @johlindquist/kit-action@v1`\r\n\r\n## Please ask for help! 😇\r\n\r\nI'd ❤️ to help you script something for a github action! Please let me know whatever I can do to help.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-09-21T19:34:15Z"},{"menu":"Drag demo","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/405","url":"","title":"beta.55 Improved Search, Drag, and Happiness 😊","command":"beta55-improved-search-drag-and-happiness","content":"## Search Improvements\r\n\r\nbeta.55 has a vastly improved search:\r\n\r\nSearch descriptions 🎉\r\n\r\n![CleanShot 2021-08-20 at 13 37 44](https://user-images.githubusercontent.com/36073/130285547-24f111d7-a706-4be2-b0d6-b425afbe6683.png)\r\n\r\nSearch shortcuts\r\n\r\n![CleanShot 2021-08-20 at 13 51 49](https://user-images.githubusercontent.com/36073/130286634-4797c029-c2ed-4071-9e1d-285d6bf1a15f.png)\r\n\r\nSearch by kenv\r\n\r\n![CleanShot 2021-08-20 at 13 51 18](https://user-images.githubusercontent.com/36073/130286567-4fd0a155-43a7-4af5-bd3f-c0a9db9ae8dc.png)\r\n\r\nSear by \"command-name\" (if you can't think of // Menu: name)\r\n\r\n![CleanShot 2021-08-20 at 13 54 45](https://user-images.githubusercontent.com/36073/130286878-62b2a139-b3b7-4c34-904f-40d7b03a7e1c.png)\r\n\r\nSorts by \"score\" (rather than alphabetically)\r\n\r\n## Drag\r\n\r\nChoices can now take a `drag` property. This will make list items \"draggable\" and allow you to drag/drop to copy files from your machine (or even from URLs!) into any app. When using remote URLs, their will be a bit of \"delay\" while the file downloads (depending on the file size) between \"drag start\" and \"drop enabled\", so just be aware. I'll add some sort of download progress indicator sometime in the future, just not high priority 😅\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Heart Eyes (local)\",\r\n drag: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n img: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n },\r\n {\r\n name: \"React logo svg (wikipedia)\",\r\n drag: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n img: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 26 07](https://user-images.githubusercontent.com/36073/130294979-c45aabe2-6c30-41ad-94b4-64a85a2c34eb.gif)\r\n\r\nYou can use the `drag` object syntax to define a `format` and `data`\r\n\r\n> `text/html`: Renders the HTML payload in contentEditable elements and rich text (WYSIWYG) editors like Google Docs, Microsoft Word, and others.\r\n> `text/plain`: Sets the value of input elements, content of code editors, and the fallback from text/html.\r\n> `text/uri-list`: Navigates to the URL when dropping on the URL bar or browser page. A URL shortcut will be created when dropping on a directory or the desktop.\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Padding 4\",\r\n drag: {\r\n format: \"text/plain\",\r\n data: `className=\"p-4\"`,\r\n },\r\n },\r\n {\r\n name: \"I love code\",\r\n drag: {\r\n format: \"text/html\",\r\n data: `I ❤️ code`,\r\n },\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 48 00](https://user-images.githubusercontent.com/36073/130296713-6249d5c2-c01f-42d1-b2c2-ea86d2e4c29b.gif)\r\n\r\n## Happiness\r\n\r\nI'm _very_ happy with the state of Script Kit. When I started almost a year ago, I had no idea I could push the concept of creating/sharing/managing custom scripts so far. I think it looks great, feels speedy, and is flexible enough to handle so, so many scenarios.\r\n\r\nWith everything in place, next week I'm starting on creating lessons, demos, and docs. It's time to show you what Script Kit can really do 😉 \r\n\r\nP.S. - Thanks for all the beta-testing and feedback. It's been tremendously helpful!\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-08-20T21:58:48Z"},{"menu":"Flags demo","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/397","url":"","title":"beta.46 Design, ⚐ Flags, div, fixed notify","command":"beta46-design-flags-div-fixed-notify","content":"## Design/theme\r\n\r\nPut a lot of work into tightening up pixels and made progress towards custom themes:\r\n\r\n![CleanShot 2021-08-13 at 09 35 40](https://user-images.githubusercontent.com/36073/129383567-ae628c68-3c96-463f-a47e-4800186ea7ac.png)\r\n\r\nHere's a silly demo of me playing with theme generation:\r\n\r\nhttps://user-images.githubusercontent.com/36073/129384214-2af744ab-8165-4e3f-825d-42fadbf86aec.mp4\r\n\r\n## Flags ⚐\r\n\r\nAn astute observer would notice that the `Edit` and `Share` tabs are now gone. They've been consolidated into a \"flag menu\".\r\n\r\nWhen you press the `right` key from the main menu of script, the flag menu now opens up. This shows the selected script and gives you some options. It also exposes the keyboard shortcuts associated with those options that you can use to :\r\n\r\n![CleanShot 2021-08-13 at 09 42 52](https://user-images.githubusercontent.com/36073/129384559-bff59ebf-88d9-4b95-b9b5-640ce755fe8f.png)\r\n\r\nI've found I use `cmd+o` and `cmd+n` all the time to tweak scripts of quickly create a new one to play around with.\r\n\r\n### Custom Flags\r\n\r\nYou can pass your own custom flags like so:\r\n\r\n[Install flags-demo](https://scriptkit.com/api/new?name=flags-demo&url=https://gist.githubusercontent.com/johnlindquist/b96c8f8de9c256f909ae0f6ab0adda39/raw/9f049cf454f0766fb278e5ee7a24c6b6776df889/flags-demo.js)\r\n\r\n```js\r\n//Menu: Flags demo\r\n\r\nlet urls = [\r\n \"https://scriptkit.com\",\r\n \"https://egghead.io\",\r\n \"https://johnlindquist.com\",\r\n]\r\n\r\nlet flags = {\r\n open: {\r\n name: \"Open\",\r\n shortcut: \"cmd+o\",\r\n },\r\n copy: {\r\n name: \"Copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n}\r\n\r\nlet url = await arg(\r\n { placeholder: `Press 'right' to see menu`, flags },\r\n urls\r\n)\r\n\r\nif (flag?.open) {\r\n $`open ${url}`\r\n} else if (flag?.copy) {\r\n copy(url)\r\n} else {\r\n console.log(url)\r\n}\r\n```\r\n\r\nNotice that `flag` is a global while `flags` is an object you pass to `arg`. This is to help keep it consistent with terminal usage:\r\n\r\nFrom the terminal\r\n```bash\r\nflags-demo --open\r\n```\r\n\r\nWill set the global `flag.open` to `true`\r\n\r\n![CleanShot 2021-08-13 at 10 08 30](https://user-images.githubusercontent.com/36073/129388037-3f27a12d-9e44-4402-a51f-bac39eead54d.png)\r\n\r\n\r\nYou could also run this and pass in all the args:\r\n\r\n```bash\r\nflags-demo https://egghead.io --copy\r\n```\r\n\r\nIn the app, you could create a second script to pass flags to the first with. This is required if you need to pass multiple flags since the `arg` helper can only \"submit\" one per `arg`.\r\n\r\n```js\r\nawait run(`flags-demo https://egghead.io --copy`)\r\n```\r\n\r\nI'll put together some more demos soon. There are plenty of existing CLI tools out there using flags heavily, so lots of inspiration to pull from.\r\n\r\n## `await div()`\r\n\r\nThere's a new `div` \"component\". You can pass in arbitrary HTML. This works well with the `md()` helper which generates `html` from markdown.\r\n\r\n[Install div-demo](https://scriptkit.com/api/new?name=div-demo&url=https://gist.githubusercontent.com/johnlindquist/0ad790953f7101d313abfd48182356b0/raw/c70e17649317986707d2ac714c31afe6f7850015/div-demo.js)\r\n\r\n```js\r\n// Menu: Div Demo\r\n\r\n// Hit \"enter\" to continue, escape to exit\r\nawait div(``)\r\n\r\nawait div(\r\n md(\r\n `\r\n # Some header\r\n\r\n ## You guessed it, an h2\r\n\r\n * I\r\n * love\r\n * lists\r\n `\r\n )\r\n)\r\n\r\n```\r\n\r\n## Fixed `notify`\r\n\r\n`notify` is now fixed so that it doesn't open a prompt\r\n\r\nThe most basic usage is:\r\n\r\n```js\r\nnotify(\"Hello world\")\r\n```\r\n\r\n`notify` leverages [https://www.npmjs.com/package/node-notifier](https://www.npmjs.com/package/node-notifier)\r\n\r\nSo the entire API should be available. Here's an example of using the \"type inside a notification\":\r\n\r\n[Install notify-demo](https://scriptkit.com/api/new?name=notify-demo&url=https://gist.githubusercontent.com/johnlindquist/44387dc5b0c170e4146b061162c33532/raw/1bce77fb778a45cf9052a63d02dcab94a9cf7ef0/notify-demo.js)\r\n\r\n```js\r\n// Menu: Notify Demo\r\nlet notifier = notify({\r\n title: \"Notifications\",\r\n message: \"Write a reply?\",\r\n reply: true,\r\n})\r\n\r\nnotifier.on(\"replied\", async (obj, options, metadata) => {\r\n await arg(metadata.activationValue)\r\n})\r\n\r\n```\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-08-13T16:33:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/365","url":"","title":"beta.33 `console.log` component, cmd+o to Open, `className`","command":"beta33-consolelog-component-cmdo-to-open-classname","content":"## `console.log` Component\r\n\r\nThe follow code will create the below prompt (👀 notice the black background logging component):\r\n```js\r\nlet { stdout } = await $`ls ~/projects | grep kit`\r\n\r\nawait arg(`Select a kit dir`, stdout.split(\"\\n\"))\r\n\r\n```\r\n\r\n\r\n```js\r\nconsole.log(chalk`{green.bold The current date is:}`)\r\nconsole.log(new Date().toLocaleDateString())\r\nawait arg()\r\n```\r\n\r\n\r\n\r\nThe log even persists between prompts:\r\n\r\n```js\r\nlet first = await arg(\"First name\")\r\nconsole.log(first)\r\nlet last = await arg(\"Last name\")\r\nconsole.log(`${first} ${last}`)\r\nlet age = await arg(\"Age\")\r\nconsole.log(`${first} ${last} ${age}`)\r\nlet emotion = await arg(\"Emotion\")\r\nconsole.log(`${first} ${last} ${age} ${emotion}`)\r\nawait arg()\r\n```\r\n\r\n\r\nClick the \"edit\" icon to open the full log in your editor:\r\n![CleanShot 2021-07-22 at 16 20 57@2x](https://user-images.githubusercontent.com/36073/126716749-4eda367a-4e55-424f-915a-30207583cd3f.png)\r\n\r\n## cmd+o to Open\r\n\r\nFrom the main menu, hitting `cmd+o` will open:\r\n\r\n1. The currently selected script from the main menu\r\n2. The currently running script\r\n3. Any \"choice\" that provides a \"filePath\" prop:\r\n\r\n```js\r\nawait arg(`cmd+o to open file`, [\r\n {\r\n name: \"Karabiner config\",\r\n filePath: \"~/.dotfiles/karabiner/karabiner.edn\",\r\n },\r\n {\r\n name: \"zshrc\",\r\n filePath: \"~/.zshrc\",\r\n },\r\n])\r\n```\r\n\r\nI've found this really useful when I want to tweak the running script, but I don't want to go back through the process of finding it.\r\n\r\n## Experimental `className`\r\n\r\nYou can pass `className` into the arg options to affect the container for the list items or panel. Most classes from Tailwind should be available. Feel free to play around with it and let me know how it goes 😇:\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n `\r\n
Working on Script Kit today
\r\n `\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n [\"Eat\", \"more\", \"tacos 🌮\"]\r\n)\r\n```\r\n\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-07-22T22:44:19Z"},{"image":"https://placekitten.com/64","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/353","url":"","title":"beta.29 M1 build, install remote kenvs, polish, upcoming lessons","command":"beta29-m1-build-install-remote-kenvs-polish-upcoming-lessons","content":"I'm starting on lessons/docs on Monday. If you have anything specific you want me to cover, please reply below!\r\n\r\n## M1 Build\r\n If you're on an M1 mac, you can download the new M1 build from https://www.scriptkit.com/\r\n\r\n1. Download https://www.scriptkit.com/\r\n2. Quit Kit. *note - typing `kit quit` or `k q` in the app is the fastest way to quit.\r\n3. Drag/drop to overwrite your previous build\r\n4. Kit should now auto-update from the M1 channel\r\n5. Open Kit\r\n\r\n## Kenv Management\r\nThere are a lot of tools to help manage other kenvs. They're in the `Kit` menu and once you've installed a remote kenv (which is really just a git repo with a scripts dir), then more options show up in the `Edit` menu to move scripts between kenvs, etc. I'll cover this in detail in the docs/lessons\r\n\r\n## Polish\r\nLots of UI work:\r\n* Remembering position - Each script with a `//Shortcut` will remember its last individual prompt position. For example, if you have a script that uses `textarea`, then drag it to the upper right, the next time you launch that script, it will launch in that position.\r\n* `//Image` metadata - Scripts can now have images:\r\n```js\r\n//Image: https://placekitten.com/64\r\n```\r\nor\r\n```js\r\n//Image: logo.png\r\n```\r\nwill load from `~/.kenv/assets/logo.png`\r\n\r\n\r\n* Spinner - added a spinner for when you submit a prompt and the process needs to do some work before opening the next prompt\r\n\r\n![CleanShot 2021-07-16 at 12 22 58](https://user-images.githubusercontent.com/36073/125992326-7b6f0034-00e8-41df-9ca7-f0e33becf0b2.gif)\r\n\r\n\r\n* Resizing - *Lots* of work on getting window resizing behavior consistent between different UIs. This was a huge pain, but you'll probably never appreciate it 😅\r\n* Lots more - many more small things\r\n\r\n## Lessons!\r\n\r\nI'm starting to work on lessons next week and getting back into streaming schedule. I would ♥️ to hear any specific questions or lessons you would like to see to help you remove some friction from your day. I'll be posting the lessons over on [egghead.io](egghead.io) for your viewing pleasure. Please ask questions in the replies!\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-07-16T18:29:00Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/330","url":"","title":"Beta.20 MOAR SPEED! ⚡️","command":"beta20-moar-speed","content":"## Process Pools and Virtualized Lists\r\n\r\nhttps://user-images.githubusercontent.com/36073/123519955-7fdd8200-d66b-11eb-8167-09b9daed1c9f.mp4\r\n\r\n\r\n## Experimental `textarea`\r\n\r\nFeel free to play around with the `textarea` for multiline input.\r\n\r\n```js\r\nlet value = await textarea()\r\n```\r\n\r\nThe API of textarea will change (it currently just sets the placeholder), but it will always return the string value of the textarea, so there won't be any breaking changes if you just keep the default behavior. `cmd+s` submits. `cmd+w` cancels.\r\n\r\n## Experimental `editor` (this will become a _paid_ 💵 feature later this year)\r\n\r\nAs an upgrade to `textarea`, `await editor()` will give you a full editor experience. Same as the textarea, the API will also change, but will always return a string of the content.\r\n\r\n\r\n```js\r\n// Defaults to markdown\r\nlet value = await editor()\r\n```\r\n\r\n> ⚠️ API is subject to change!\r\n```js\r\nlet value = await editor(\"markdown\", `\r\n## Preloaded content\r\n\r\n* nice\r\n`)\r\n```\r\n\r\n```js\r\nlet value = await editor(\"javascript\", `\r\nconsole.log(\"Support other languages\")\r\n`)\r\n```\r\n\r\n### A note on paid features\r\n\r\nEverything you've used so far in the Script Kit app will stay free. The core `kit` is open-source MIT. \r\n\r\nThe paid features will be add-ons to the core experience: Themes, Editor, Widgets, Screenshots, Record Audio, and many more fun ideas. These will roll out experimentally in the free version first then move exclusively to the paid version. Expect the paid versions later this year.\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-26T17:03:40Z"},{"shortcut":"option 5","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/312","url":"","title":"Beta.19 New Features - Gotta go fast! 🏎💨","command":"beta19-new-features-gotta-go-fast","content":"Beta.19 is all about _speed_! I've finally landed on an approach I love to get the prompt moving waaaay faster.\r\n\r\nCouple videos below:\r\n\r\n## Instant Prompts\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084054-6d270a00-c79d-11eb-8b37-96473de7e0e4.mp4\r\n\r\n```js\r\n// Shortcut: option 5\r\n\r\nlet { items } = await db(async () => {\r\n let response = await get(\r\n `https://api.github.com/users/johnlindquist/repos`\r\n )\r\n\r\n return response.data\r\n})\r\n\r\nawait arg(\"Select repo\", items)\r\n\r\n```\r\n\r\n## Instant Tabs\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084134-85972480-c79d-11eb-9e18-94e5a2efa5d1.mp4\r\n\r\n## Instant Main Menu\r\n\r\nThe main menu now also leverages the concepts behind Instant Prompts listed above.\r\n\r\n## Faster in the future\r\n\r\nThese conventions laid the groundwork for caching prompt data, but I still have plenty ideas to speed things, especially around how the app launches the process. I'm looking forward to making this even faster for you!\r\n\r\nI'm also starting the work on an \"Instant Textarea\" because I know popping open a little textarea to take/save notes/ideas is something many people would use. 📝\r\n\r\n\r\n\r\n\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-07T20:47:28Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/305","url":"","title":"How to Get Your Scripts Featured on ScriptKit.com 😎","command":"how-to-get-your-scripts-featured-on-scriptkitcom","content":"TL;DR\r\n\r\n- Help -> Create kenv\r\n- Git init new kenv, push to github\r\n- Reply, dm, contact me somehow with the repo 😇\r\n\r\nHere's a video walking you through it:\r\n\r\nhttps://user-images.githubusercontent.com/36073/120856653-6732ee00-c53d-11eb-9dfb-04907b036361.mp4\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-04T20:07:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/304","url":"","title":"Beta.18 Changes/Features (`db` has a breaking change)","command":"beta18-changesfeatures-db-has-a-breaking-change","content":"## ⚠️Breaking: New `db` helper\r\n\r\n[lowdb](https://github.com/typicode/lowdb) updated to 2.0, so I updated the `db` helper to support it.\r\n\r\n* access/mutate the objects in the db directly. Then `.write()` to save your changes to the file.\r\n* `await db()` and `await myDb.write()`\r\n\r\nExample with a simple object:\r\n```js\r\nlet shoppingListDb = await db(\"shopping-list\", {\r\n list: [\"apples\", \"bananas\"],\r\n})\r\n\r\nlet item = await arg(\"Add to list\")\r\nshoppingListDb.list.push(item)\r\nawait shoppingListDb.write()\r\n\r\nawait arg(\"Shopping list\", shoppingListDb.list)\r\n```\r\n\r\n\r\nYou can also use an `async` function to store the initial data:\r\n```js\r\nlet reposDb = await db(\"repos\", async () => {\r\n let response = await get(\r\n \"https://api.github.com/users/johnlindquist/repos\"\r\n )\r\n\r\n return {\r\n repos: response.data,\r\n }\r\n})\r\n\r\nawait arg(\"Select repo\", reposDb.repos)\r\n```\r\n\r\n## Text Area prompt\r\n\r\n```js\r\nlet text = await textarea()\r\n\r\ninspect(text)\r\n```\r\n![CleanShot 2021-06-04 at 14 25 12](https://user-images.githubusercontent.com/36073/120858988-cf370380-c540-11eb-8b79-5483ec090dd8.gif)\r\n\r\n\r\n## Optional `value`\r\n\r\n`arg` choice objects used to require a `value`. Now if you don't provide a value, it will simply return the entire object:\r\n\r\n```js\r\nlet person = await arg(\"Select\", [\r\n { name: \"John\", location: \"Chair\" },\r\n { name: \"Mindy\", location: \"Couch\" },\r\n])\r\n\r\nawait arg(person.location)\r\n```\r\n\r\n## ⚗️ Experimental \"Multiple kenvs\"\r\n\r\nThere was a _ton_ 🏋️♀️ of internal work over the past couple weeks to get this working. The \"big idea\" is supporting multiple kit environments. For example:\r\n\r\n* private/personal kenv\r\n* shared kenv\r\n* company kenv\r\n* product kenv\r\n\r\n### Future plans\r\nIn an upcoming release: \r\n* you'll be able to \"click to install kenv from repo\" (just like we do with individual scripts)\r\n* update a git-controlled kenv (like a company kenv)\r\n* the main prompt will be able to search for all scripts across kenvs. \r\n* If multiple kenvs exist, creating a new script will ask you which kenv to create it in.\r\n\r\nFor now, you can try adding/creating/switching the help menu. It should all work fine, but will be _waaaay_ cooler in the future 😎\r\n\r\n![CleanShot 2021-06-04 at 11 50 32](https://user-images.githubusercontent.com/36073/120843227-16b29500-c52b-11eb-974c-a81c260b9ae2.png)\r\n\r\n## Improved Error Prompt\r\n\r\nNow when an error occurs, it takes the error data, shuts down the script, then prompts you on what to do. For example, trying to use the old `db` would result in this:\r\n\r\n![CleanShot 2021-06-04 at 12 03 04](https://user-images.githubusercontent.com/36073/120844575-d6541680-c52c-11eb-8d12-c7c3117e132e.png)\r\n\r\n## Improved Tab Switching\r\nSwitching tabs will now cancel the previous tabs' script. Previously, if you quickly switched tabs on the main menu, the \"Hot\" tab results might show up in a different tab because the loaded _after_ the tab switched. The internals around message passing between the script and the app now have a cancellation mechanism so you only get the latest result that matches the prompt/tab. (This was also a ton of internals refactoring work 😅)\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-06-04T18:09:55Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/282","url":"","title":"✨NEW FEATURES✨ beta.17","command":"new-features-beta17","content":"New features are separated into the comments below:\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-05-18T20:24:57Z"},{"background":"true","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/245","url":"","title":"✨ NEW ✨ // Background: true","command":"new-background-true","content":"`beta.12` brings in the ability to start/stop background tasks.\r\n\r\n\r\nUsing `// Background :true` at the top of your script will change the behavior in the main menu:\r\n```js\r\n// Background: true\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## Auto (like nodemon)\r\n```js\r\n// Background: auto\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\nUsing `auto`, after you start the script, editing will stop/restart the script.\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-05-06T19:36:22Z"},{"watch":"~/projects/thoughts/**/*.md","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/213","url":"","title":"// Watch: metadata 👀","command":"watch-metadata","content":"Script Kit now supports `// Watch:` metadata\r\n\r\n```js\r\n// Watch: ~/projects/thoughts/**/*.md\r\n\r\nlet { say } = await kit(\"speech\")\r\n\r\nsay(\"journal updated\")\r\n```\r\n\r\n* `// Watch: ` supports any file name, glob, or array (Kit will `JSON.parse` the array).\r\n* Scripts will run on the \"change\" event\r\n* Read more about supported [globbing](https://github.com/micromatch/picomatch#globbing-features)\r\n\r\n> Read about the [other metadata](https://github.com/johnlindquist/kit/discussions/185)\r\n\r\nI would _LOVE_ to hear about scenarios you would use this for or if you run into any issues 🙏","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-29T14:31:31Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/150","url":"","title":"beta.96 - Design, Drop, and Hotkeys! Oh my!","command":"beta96-design-drop-and-hotkeys-oh-my","content":"\r\nhttps://user-images.githubusercontent.com/36073/115079813-fc5f2200-9ebe-11eb-8e7c-74c8a1d2aee3.mp4\r\n\r\nCan't wait to see what you build! Happy Scripting this weekend! 😇","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-16T20:32:44Z"},{"menu":"Google Image Search","description":"","author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/119","url":"","title":"*New* Choice Preview","command":"new-choice-preview","content":"\r\nhttps://user-images.githubusercontent.com/36073/114220248-fc907800-9928-11eb-8096-61a5debbdc0d.mp4\r\n\r\n\r\n[Install google-image-search](https://scriptkit.app/api/new?name=google-image-search&url=https://gist.githubusercontent.com/johnlindquist/99756d4e1a54c737dc534c4edb5f6c9d/raw/55c440503a8a653c3ef3dafb9ba1bd567fc0b14a/google-image-search.js)\r\n\r\n```js\r\n// Menu: Google Image Search\r\n// Description: Searches Google Images\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nlet gis = await npm(\"g-i-s\")\r\n\r\nlet selectedImageUrl = await arg(\r\n \"Image search:\",\r\n async input => {\r\n if (input.length < 3) return []\r\n\r\n let searchResults = await new Promise(res => {\r\n gis(input, (_, results) => {\r\n res(results)\r\n })\r\n })\r\n\r\n return searchResults.map(({ url }) => {\r\n return {\r\n name: url.split(\"/\").pop().replace(/\\?.*/g, \"\"),\r\n value: url,\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\ncopy(selectedImageUrl)\r\n\r\n```\r\n\r\n\r\n\r\n[Install giphy-search](https://scriptkit.app/api/new?name=giphy-search&url=https://gist.githubusercontent.com/johnlindquist/dc17a3f07fb41b855e742a0f995cb0ed/raw/109831f9d40a8293b7d8741b44081fddcb024cda/giphy-search.js)\r\n\r\n```js\r\n// Menu: Giphy\r\n// Description: Search giphy. Paste markdown link.\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\nlet download = await npm(\"image-downloader\")\r\nlet queryString = await npm(\"query-string\")\r\nlet { setSelectedText } = await kit(\"text\")\r\n\r\nif (!env.GIPHY_API_KEY) {\r\n show(\r\n `
\r\n
\r\n Grab an API Key from the Giphy dev dashboard:\r\n
`\r\n )\r\n}\r\nlet GIPHY_API_KEY = await env(\"GIPHY_API_KEY\")\r\n\r\nlet search = q =>\r\n `https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${q}&limit=10&offset=0&rating=g&lang=en`\r\n\r\nlet { input, url } = await arg(\r\n \"Search giphy:\",\r\n async input => {\r\n if (!input) return []\r\n let query = search(input)\r\n let { data } = await get(query)\r\n\r\n return data.data.map(gif => {\r\n return {\r\n name: gif.title.trim() || gif.slug,\r\n value: {\r\n input,\r\n url: gif.images.downsized_medium.url,\r\n },\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\nlet formattedLink = await arg(\"Format to paste\", [\r\n {\r\n name: \"URL Only\",\r\n value: url,\r\n },\r\n {\r\n name: \"Markdown Image Link\",\r\n value: `![${input}](${url})`,\r\n },\r\n {\r\n name: \"HTML \",\r\n value: ``,\r\n },\r\n])\r\n\r\nsetSelectedText(formattedLink)\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-09T21:43:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/112","url":"","title":"Types are here!","command":"types-are-here","content":"Update (1.1.0-beta.86) adds a [`~/.kit/kit.d.ts`](https://github.com/johnlindquist/kit/blob/main/kit.d.ts) to allow better code hinting and completion.\r\n\r\n❗️After updating, you will need to manually \"link\" your `~/.kenv` to your `~/.kit` for the benefits (This will happen automatically for new users during install)\r\n\r\nMethod 1 - Install and run this script\r\n\r\n[Click to install link-kit](https://scriptkit.app/api/new?name=link-kit&url=https://gist.githubusercontent.com/johnlindquist/f238cb1b3a3ed97890657ccf154d12b1/raw/a488a8b6c331d527bb0433a6b8df9428263b85a0/link-kit.js)\r\n\r\n```js\r\nawait cli(\"install\", \"~/.kit\")\r\n```\r\n\r\nMethod 2 - In your terminal\r\n```bash\r\nPATH=~/.kit/node/bin ~/.kit/node/bin/npm --prefix ~/.kenv i ~/.kit\r\n```\r\n\r\nNow your scripts in your `~/.kenv/scripts` should have completion/hinting for globals included in the \"preloaded\" scripts.\r\n\r\n> I still need to add types for the helpers that load scripts from dirs `kit()`, `cli()`, etc.\r\n\r\nPlease let me know how it goes and if you have any questions. Thanks!","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-04-03T18:08:24Z"}]
diff --git a/apps/scriptkit/public/data/docs.json b/apps/scriptkit/public/data/docs.json
index 09fa817..22df064 100644
--- a/apps/scriptkit/public/data/docs.json
+++ b/apps/scriptkit/public/data/docs.json
@@ -1 +1 @@
-[{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/826","url":"","title":"Run From the Terminal","command":"run-from-the-terminal","content":"\n\n\n\n\n \n\n\nIn progress...","extension":".md","dir":"docs","file":"run-from-the-terminal","description":"","tag":"","section":"Advanced","i":"1","sectionIndex":"5","createdAt":"2022-07-06T17:51:54Z"},{"name":"Get Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/825","url":"https://gist.githubusercontent.com/johnlindquist/cdef2447a1b49ad163a9c696369c930d/raw/65407970a7dd1af406b0cbee94e876e84822e16e/get-example.js","title":"Get, Post, Put, Delete Requests","command":"get-post-put-delete-requests","content":"\n\n\n\n\n \n\n\nThe `get`, `post`, `put`, and `delete` methods use the [axios](https://www.npmjs.com/package/axios) API\n\n## Make a Get Request\n\n```js\n// Name: Get Example\n\nimport \"@johnlindquist/kit\"\n\nlet response = await get(\n \"https://scriptkit.com/api/get-example\"\n)\n\nawait div(md(response.data.message))\n```\n\n[Open get-example in Script Kit](https://scriptkit.com/api/new?name=get-example&url=https://gist.githubusercontent.com/johnlindquist/cdef2447a1b49ad163a9c696369c930d/raw/65407970a7dd1af406b0cbee94e876e84822e16e/get-example.js\")\n\n## Make a Post Request\n\n```js\n// Name: Post Example\n\nimport \"@johnlindquist/kit\"\n\nlet response = await post(\n \"https://scriptkit.com/api/post-example\",\n {\n name: await arg(\"Enter your name\"),\n }\n)\n\nawait div(md(response.data.message))\n```\n\n[Open post-example in Script Kit](https://scriptkit.com/api/new?name=post-example&url=https://gist.githubusercontent.com/johnlindquist/8bac8a4e303fe21c93b93787c828419f/raw/fcb26d4eafa0fa36fcb88b02716e4503e130b375/post-example.js\")\n","extension":".md","dir":"docs","file":"get-post-put-delete-requests","description":"","tag":"","section":"Files and Data","i":"3","sectionIndex":"3","createdAt":"2022-07-06T17:51:54Z"},{"menu":"Database Read/Write Example","description":"","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/823","url":"https://gist.githubusercontent.com/johnlindquist/a7cda43e196f6b6e38e4c66cba8cdb74/raw/8d93dc14970bac042763cb86b30456b32ba5fab7/db-store.js","title":"Simple Storage","command":"simple-storage","content":"\n\n\n\n\n \n\n\n## Read and Write Using the `db` Helper\n\nThe `db` helpers reads/writes to json files in the `~/.kenv/db` directory. It's meant as a simple wrapper around common json operations.\n\n```js\n// Menu: Database Read/Write Example\n// Description: Add/remove items from a list of fruit\n\nlet fruitDb = await db([\"apple\", \"banana\", \"orange\"])\n\n// This will keep prompting until you hit Escape\nwhile (true) {\n let fruitToAdd = await arg(\n {\n placeholder: \"Add a fruit\",\n //allows to submit input not in the list\n strict: false,\n },\n fruitDb.items\n )\n\n fruitDb.items.push(fruitToAdd)\n await fruitDb.write()\n\n let fruitToDelete = await arg(\n \"Delete a fruit\",\n fruitDb.items\n )\n\n fruitDb.items = fruitDb.items.filter(\n fruit => fruit !== fruitToDelete\n )\n\n await fruitDb.write()\n}\n```\n\n[Open db-store in Script Kit](https://scriptkit.com/api/new?name=db-store&url=https://gist.githubusercontent.com/johnlindquist/a7cda43e196f6b6e38e4c66cba8cdb74/raw/8d93dc14970bac042763cb86b30456b32ba5fab7/db-store.js\")\n","extension":".md","dir":"docs","file":"simple-storage","tag":"","section":"Files and Data","i":"4","sectionIndex":"3","createdAt":"2022-07-06T16:05:47Z"},{"name":"Play with Data in Chrome DevTools","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/822","url":"https://gist.githubusercontent.com/johnlindquist/3202a35d448efd09c37c4b49b7f7c95a/raw/187da03b4dae7c1ebe6fb79bd1ea47f7a492cb38/play-with-data-in-chrome-devtools.js","title":"DevTools","command":"devtools","content":"\n\n\n\n\n \n\n\n## Play with Data in Chrome DevTools\n\n```js\n// Name: Play with Data in Chrome DevTools\n\nimport \"@johnlindquist/kit\"\n\n// Will open a standalone Chrome DevTools window\n// The object passed in will be displayed\n// You can access the object using `x`, e.g., `x.message` will be `Hello world`\ndev({\n message: \"Hello world\",\n})\n```\n\n[Open play-with-data-in-chrome-devtools in Script Kit](https://scriptkit.com/api/new?name=play-with-data-in-chrome-devtools&url=https://gist.githubusercontent.com/johnlindquist/3202a35d448efd09c37c4b49b7f7c95a/raw/187da03b4dae7c1ebe6fb79bd1ea47f7a492cb38/play-with-data-in-chrome-devtools.js\")\n","extension":".md","dir":"docs","file":"devtools","description":"","tag":"","section":"Essentials","i":"6","sectionIndex":"1","createdAt":"2022-07-04T20:06:34Z"},{"name":"Run Commands in the Terminal","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/821","url":"https://gist.githubusercontent.com/johnlindquist/e1fbd791d67d772c047f2afcab087cff/raw/37f5ab92f8caafbf437d4f234818ebae67fb7fba/run-commands-in-the-terminal.js","title":"Built-in Terminal","command":"built-in-terminal","content":"\n\n\n\n\n \n\n\n## Use the Built-in Terminal\n\n```js\n// Name: Run Commands in the Terminal\n\nimport \"@johnlindquist/kit\"\n\nawait term({\n //defaults to home dir\n cwd: `~/.kenv`,\n command: `ls`,\n // The footer is optional. All terms continue with the same shortcuts\n footer: `ctrl+c or cmd+enter to continue`,\n})\n```\n\n[Open run-commands-in-the-terminal in Script Kit](https://scriptkit.com/api/new?name=run-commands-in-the-terminal&url=https://gist.githubusercontent.com/johnlindquist/e1fbd791d67d772c047f2afcab087cff/raw/37f5ab92f8caafbf437d4f234818ebae67fb7fba/run-commands-in-the-terminal.js\")\n\n> The shell defaults to `zsh`. You can change your shell by setting the `KIT_SHELL` environment variable in the ~/kenv/.env, but most of the testing has been done with `zsh`.\n","extension":".md","dir":"docs","file":"built-in-terminal","description":"","tag":"","section":"Essentials","i":"4","sectionIndex":"1","createdAt":"2022-07-04T20:06:33Z"},{"name":"Input Text","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/819","url":"https://gist.githubusercontent.com/johnlindquist/af8883b05ae34055fff79ec8556e007d/raw/bb5d116f831d3124081867e83710a07e39bf41cd/input-text.js","title":"Text Input","command":"text-input","content":"\n\n\n\n\n \n\n\n## Input Text with `await arg()`\n\nThe simplest form of input you can accept from a user is an `arg()`\n\n```js\n// Name: Input Text\n\nimport \"@johnlindquist/kit\"\n\nlet name = await arg(\"Enter your name\")\n\nawait div(md(`Hello, ${name}`))\n```\n\n[Open input-text in Script Kit](https://scriptkit.com/api/new?name=input-text&url=https://gist.githubusercontent.com/johnlindquist/af8883b05ae34055fff79ec8556e007d/raw/bb5d116f831d3124081867e83710a07e39bf41cd/input-text.js\")\n\n## Select From a List of Strings\n\n```js\n// Name: Select From a List\n\nimport \"@johnlindquist/kit\"\n\nlet fruit = await arg(\"Pick a fruit\", [\n \"Apple\",\n \"Banana\",\n \"Cherry\",\n])\n\nawait div(md(`You selected ${fruit}`))\n```\n\n[Open select-from-a-list in Script Kit](https://scriptkit.com/api/new?name=select-from-a-list&url=https://gist.githubusercontent.com/johnlindquist/a53ebfe6372eb3ad3aade06e2d11ef51/raw/b1939b6cceb669f2bbaeec5e6b3af2549994e214/select-from-a-list.js\")\n\n## Select From a List of Objects\n\n```js\n// Name: Select From a List of Objects\n\nimport \"@johnlindquist/kit\"\n\nlet { size, weight } = await arg(\"Select a Fruit\", [\n {\n name: \"Apple\",\n description: \"A shiny red fruit\",\n // add any properties to \"value\"\n value: {\n size: \"small\",\n weight: 1,\n },\n },\n {\n name: \"Banana\",\n description: \"A long yellow fruit\",\n value: {\n size: \"medium\",\n weight: 2,\n },\n },\n])\n\nawait div(\n md(\n `You selected a fruit with size: ${size} and weight: ${weight}`\n )\n)\n```\n\n[Open select-from-a-list-of-objects in Script Kit](https://scriptkit.com/api/new?name=select-from-a-list-of-objects&url=https://gist.githubusercontent.com/johnlindquist/1643c1f34cc146e19c01b5144c542b6f/raw/ac3a2bc71c27d1ee58cf83394c8755a005d2a567/select-from-a-list-of-objects.js\")\n\n## Display a Preview When Focusing a Choice\n\n```js\n// Name: Display a Preview When Focusing a Choice\n\nimport \"@johnlindquist/kit\"\n\nlet heights = [320, 480, 640]\nlet choices = heights.map(h => {\n return {\n name: `Kitten height: ${h}`,\n preview: () =>\n `\n\n\n\n\n \n\n\n## Display HTML\n\nUse `await div('')` to display HTML.\n\n```js\n// Name: Display HTML\n\nimport \"@johnlindquist/kit\"\n\nawait div(`
Hello World
`)\n```\n\n[Open display-html in Script Kit](https://scriptkit.com/api/new?name=display-html&url=https://gist.githubusercontent.com/johnlindquist/ba1d6754436d898f8cebe8558647e720/raw/468e99941e8c63eff51ba24b6cb7c86bb9dd70fe/display-html.js\")\n\n## Display HTML with CSS\n\nScript Kit bundles [Tailwind CSS](https://tailwindcss.com/).\n\n```js\n// Name: Display HTML with CSS\n\nimport \"@johnlindquist/kit\"\n\nawait div(\n `
Hello World
`\n)\n```\n\n[Open display-html-with-css in Script Kit](https://scriptkit.com/api/new?name=display-html-with-css&url=https://gist.githubusercontent.com/johnlindquist/02b7a43e5dd49f2e1508d8c110d12371/raw/1d80190f0cfce860078cec799fd614bd6f49a474/display-html-with-css.js\")\n\n## Display Markdown\n\nThe `md()` function will convert Markdown to HTML into HTML that you can pass into div. It will also add the default Tailwind styles so you won't have to think about formatting.\n\n```js\n// Name: Display Markdown\n\nimport \"@johnlindquist/kit\"\n\nlet html = md(`# Hello World`)\n\nawait div(html)\n```\n\n[Open display-markdown in Script Kit](https://scriptkit.com/api/new?name=display-markdown&url=https://gist.githubusercontent.com/johnlindquist/84779dbf8e39212c672b16ee72c68ccf/raw/7e985c988fa6aa878e4c0040dac6b87b8cfb173c/display-markdown.js\")\n","extension":".md","dir":"docs","file":"display-html-and-markdown","description":"","tag":"","section":"Essentials","i":"0","sectionIndex":"1","createdAt":"2022-07-02T01:52:44Z"},{"name":"Speak File","watch":"~/speak.txt","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/814","url":"https://gist.githubusercontent.com/johnlindquist/ec65920283c6ef66429a2331cdc81539/raw/98584e1ee1cb6b5f4f235a6873bdfcb709dfb953/speak-file.js","title":"Watch for File Changes","command":"watch-for-file-changes","content":"\n\n\n\n\n \n\n\n## Watch a Single File\n\nThe `// Watch` metadata enables you to watch for changes to a file on your system.\n\n```js\n// Name: Speak File\n// Watch: ~/speak.txt\n\nimport \"@johnlindquist/kit\"\n\nlet speakPath = home(\"speak.txt\")\n\ntry {\n let content = await readFile(speakPath, \"utf-8\")\n if (content.length < 60) {\n // We don't want `say` to run too long 😅\n say(content)\n }\n} catch (error) {\n log(error)\n}\n```\n\n[Open speak-file in Script Kit](https://scriptkit.com/api/new?name=speak-file&url=https://gist.githubusercontent.com/johnlindquist/ec65920283c6ef66429a2331cdc81539/raw/98584e1ee1cb6b5f4f235a6873bdfcb709dfb953/speak-file.js\")\n\n## Watch a Directory\n\nThe `// Watch` metadata uses [Chokidar](https://www.npmjs.com/package/chokidar) under the hood, so it supports the same glob patterns.\n\n```js\n// Name: Download Log\n// Watch: ~/Downloads/*\n\nimport \"@johnlindquist/kit\"\n\n// These are optional and automatically set by the watcher\nlet filePath = await arg()\nlet event = await arg()\n\nif (event === \"add\") {\n await appendFile(home(\"download.log\"), filePath + \"\\n\")\n}\n```\n\n[Open download-log in Script Kit](https://scriptkit.com/api/new?name=download-log&url=https://gist.githubusercontent.com/johnlindquist/395ced3283e44c8ed2fea885104a1346/raw/e9b03ddfb0ae969d2b82e45c41ac8680ea2e686b/download-log.js\")\n","extension":".md","dir":"docs","file":"watch-for-file-changes","description":"","tag":"","section":"Script Metadata","i":"2","sectionIndex":"2","createdAt":"2022-06-30T02:17:17Z"},{"name":"Express Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/813","url":"https://gist.githubusercontent.com/johnlindquist/52c9ab749f8483a15ccbd28631db2df1/raw/e8bc1e1dec05fd17e36cafbf21ada409b91a6fa9/express-example.js","title":"npm Packages","command":"npm-packages","content":"\n\n\n\n\n \n\n\n## Install Express from npm\n\n```js\n// Name: Express Example\n\nimport \"@johnlindquist/kit\"\n\nlet express = await npm(\"express\")\nlet detect = await npm(\"detect-port\")\n\nlet app = express()\n\napp.get(\"/\", (req, res) => {\n res.send(`Hello Script Kit!`)\n})\n\nlet port = await detect()\napp.listen(port)\n\nawait hide() // in case the terminal is open from installing packages\n\nawait browse(`http://localhost:${port}`)\n```\n\n[Open express-example in Script Kit](https://scriptkit.com/api/new?name=express-example&url=https://gist.githubusercontent.com/johnlindquist/52c9ab749f8483a15ccbd28631db2df1/raw/e8bc1e1dec05fd17e36cafbf21ada409b91a6fa9/express-example.js\")\n\n> You can terminate a long-running process like above from the menubar dropdown menu or by pressing `cmd+p` from the Script Kit window to list running processes.\n","extension":".md","dir":"docs","file":"npm-packages","description":"","tag":"","section":"Essentials","i":"7","sectionIndex":"1","createdAt":"2022-06-30T02:17:17Z"},{"name":"Select a Path","note":"Dropping one or more files returns an array of file information","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/809","url":"https://gist.githubusercontent.com/johnlindquist/68ae880d76f6d92b1aa9994501465f2b/raw/839a08ef025a07e5d5e292c8730d7c631b934798/select-a-path.js","title":"Select a Path","command":"select-a-path","content":"\n\n\n\n\n \n\n\n## Select a Path\n\n```js\n// Name: Select a Path\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = await path()\n\nawait div(md(`You selected ${filePath}`))\n```\n\n[Open select-a-path in Script Kit](https://scriptkit.com/api/new?name=select-a-path&url=https://gist.githubusercontent.com/johnlindquist/68ae880d76f6d92b1aa9994501465f2b/raw/839a08ef025a07e5d5e292c8730d7c631b934798/select-a-path.js\")\n\n## Select a Path with Options\n\n```js\n// Name: Select a Path with Options\n\nimport \"@johnlindquist/kit\"\n\nawait path({\n hint: `Select a path containing JS files`,\n onlyDirs: true,\n onChoiceFocus: async (input, { focused }) => {\n let focusedPath = focused.value\n try {\n let files = await readdir(focusedPath)\n let hasJS = files.find(f => f.endsWith(\".js\"))\n\n setPreview(\n md(\n `${\n hasJS ? \"✅ Found\" : \"🔴 Didn't find\"\n } JS files`\n )\n )\n } catch (error) {\n log(error)\n }\n },\n})\n```\n\n[Open select-a-path-with-options in Script Kit](https://scriptkit.com/api/new?name=select-a-path-with-options&url=https://gist.githubusercontent.com/johnlindquist/8ec7f7178cd44481aed4e968fd83da3f/raw/c8a4d8deacaa192f07fbdfed7c17a75558bc99a7/select-a-path-with-options.js\")\n\n## Drag and Drop\n\n```js\n// Name: Drop Example\n\nimport \"@johnlindquist/kit\"\n\n// Note: Dropping one or more files returns an array of file information\n// Dropping text or an image from the browser returns a string\nlet fileInfos = await drop()\n\nlet filePaths = fileInfos.map(f => f.path).join(\",\")\n\nawait div(md(filePaths))\n```\n\n[Open drop-example in Script Kit](https://scriptkit.com/api/new?name=drop-example&url=https://gist.githubusercontent.com/johnlindquist/f7937ef8b3d5827b5aaa17b59dc4e223/raw/183d7cdd3c3e687cdd4fd6fd833abd957e57d3de/drop-example.js\")\n\n## Select from Finder Prompts\n\n```js\n// Name: Select from Finder Prompt\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = await selectFile()\n\nlet folderPath = await selectFolder()\n\nawait div(md(`You selected ${filePath} and ${folderPath}`))\n```\n\n[Open select-from-finder-prompt in Script Kit](https://scriptkit.com/api/new?name=select-from-finder-prompt&url=https://gist.githubusercontent.com/johnlindquist/d27e5970cb6284bd28b746eaeb49df78/raw/5f4b446f2b5a61435a651e0132e878fae9a4f819/select-from-finder-prompt.js\")\n","extension":".md","dir":"docs","file":"select-a-path","description":"","tag":"","section":"Essentials","i":"2","sectionIndex":"1","createdAt":"2022-06-30T02:17:14Z"},{"name":"Stand Up and Stretch","schedule":"*/15 * * * *","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/806","url":"","title":"Run a Script on a Schedule","command":"run-a-script-on-a-schedule","content":"\n\n\n\n\n \n\n\n## Run a Script on a Schedule\n\nUse cron syntax to run scripts on a schedule. The following example will show a notification to stand up and stretch every 15 minutes.\n\n```js\n// Name: Stand Up and Stretch\n// Schedule: */15 * * * *\n\nimport \"@johnlindquist/kit\"\n\nnotify(`Stand up and stretch`)\n```\n\n[Open stand-up-and-stretch in Script Kit](https://scriptkit.com/api/new?name=stand-up-and-stretch&url=https://gist.githubusercontent.com/johnlindquist/4a857741902927cc97e10db7a43b497d/raw/e01f61d697941fe0f0e90d51d5eb35f81b214be7/stand-up-and-stretch.ts\")\n\n[Crontab.guru](https://crontab.guru/) is a great utility to help generate and understand cron syntax.\n","extension":".md","dir":"docs","file":"run-a-script-on-a-schedule","description":"","tag":"","section":"Script Metadata","i":"1","sectionIndex":"2","createdAt":"2022-06-30T02:17:13Z"},{"name":"Read Files","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/805","url":"https://gist.githubusercontent.com/johnlindquist/00093125f3735bed8062dce0e20af4f8/raw/faec10fc8f99cc0744e9e840cfadb6cbe88e3a32/read-files.js","title":"Read Files","command":"read-files","content":"\n\n\n\n\n \n\n\n## Read Files\n\n```js\n// Name: Read Files\n\nimport \"@johnlindquist/kit\"\n\nlet pkg = await readFile(\n home(\".kenv\", \"package.json\"),\n \"utf-8\"\n)\n\nawait editor({\n value: pkg,\n language: \"json\",\n})\n```\n\n[Open read-files in Script Kit](https://scriptkit.com/api/new?name=read-files&url=https://gist.githubusercontent.com/johnlindquist/00093125f3735bed8062dce0e20af4f8/raw/faec10fc8f99cc0744e9e840cfadb6cbe88e3a32/read-files.js\")\n","extension":".md","dir":"docs","file":"read-files","description":"","tag":"","section":"Files and Data","i":"0","sectionIndex":"3","createdAt":"2022-06-30T02:17:12Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/803","url":"","title":"Share Scripts","command":"share-scripts","content":"\n\n\n\n\n \n\n\n## Get Featured\n\nFeatured scripts are displayed in:\n\n- The `hot` tab of the Script Kit main window\n- On the [Community Scripts](https://www.scriptkit.com/scripts) page\n\nTo get featured, post your script to the [Script Kit Github discussions Share page](https://github.com/johnlindquist/kit/discussions/categories/share). With a script focused in the Script Kit main window, you can press right or cmd+k to bring up a share menu which will automatically walk you through creating a shareable post for the script.\n\nAs a shortcut, hit cmd+s with a script selected to automatically run the \"Share as Discussion\" process.\n\n## Share as a Gist, Link, URL, or Markdown\n\nThe Script Kit main window also includes many other share options:\n\n- Share as Gist cmd+g: Creates as Gist of the selected script, then copies the URL to the clipboard\n- Share as Link opt+s: Creates a private installable kit://link to the selected script, then copies the URL to the clipboard. These links are very long as they encode the entire script into the URL.\n- Share as URL opt+u: Creates a Gist of the selected script, then copies an installable public URL to the clipboard\n- Share as Markdown cmd+m: Copies the selected script as a Markdown snippet to the clipboard\n","extension":".md","dir":"docs","file":"share-scripts","description":"","tag":"","section":"Essentials","i":"8","sectionIndex":"1","createdAt":"2022-06-30T02:17:11Z"},{"name":"Env Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/802","url":"https://gist.githubusercontent.com/johnlindquist/84068b5eb52a366b0746aff3f984f3dd/raw/c22f3160535158d2d38952b4a7ee22a105d9359f/env-example.js","title":"Environment Variables","command":"environment-variables","content":"\n\n\n\n\n \n\n\n## Read and Write from ~/.kenv/.env\n\nThe `env` helper will read environment variables from ~/.kenv/.env. If the variable doesn't exist, it will prompt you to create it.\n\n```js\n// Name: Env Example\n\nimport \"@johnlindquist/kit\"\n\nlet KEY = await env(\"MY_KEY\")\n\nawait div(md(`You loaded ${KEY} from ~/.kenv/.env`))\n```\n\n[Open env-example in Script Kit](https://scriptkit.com/api/new?name=env-example&url=https://gist.githubusercontent.com/johnlindquist/84068b5eb52a366b0746aff3f984f3dd/raw/c22f3160535158d2d38952b4a7ee22a105d9359f/env-example.js\")\n\n## Choose an Environment Variable\n\nIf you pass a function as the second argument to `env`, it will only be called if the variable doesn't exist.\nThis allows you to set Enviroment Variables from a list, an API, or any other data source.\n\n```js\n// Name: Choose an Environment Variable\n\nimport \"@johnlindquist/kit\"\n\nlet MY_API_USER = await env(\"MY_API_USER\", async () => {\n return await arg(\"Select a user for your API\", [\n \"John\",\n \"Mindy\",\n \"Joy\",\n ])\n})\n\nawait div(\n md(\n `You selected ${MY_API_USER}. Running this script again will remember your choice`\n )\n)\n```\n\n[Open choose-an-environment-variable in Script Kit](https://scriptkit.com/api/new?name=choose-an-environment-variable&url=https://gist.githubusercontent.com/johnlindquist/cbc1029ea6abcdb8658cc3919b05875c/raw/cc8ca92d9edc57e16e4fcf2978e5560c6c73ab71/choose-an-environment-variable.js\")\n","extension":".md","dir":"docs","file":"environment-variables","description":"","tag":"","section":"Essentials","i":"5","sectionIndex":"1","createdAt":"2022-06-30T02:17:10Z"},{"name":"Download a File","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/800","url":"https://gist.githubusercontent.com/johnlindquist/b10dbc2218d7c229fd4ed9865739b46f/raw/b9108056d761cdf6b1e8ec5c7d218d11f4002e56/download-a-file.js","title":"Download Files","command":"download-files","content":"\n\n\n\n\n \n\n\n## Download a Single File\n\n```js\n// Name: Download a File\n\nimport \"@johnlindquist/kit\"\n\nlet url = \"https://www.scriptkit.com/assets/logo.png\"\nlet buffer = await download(url)\n\nlet fileName = path.basename(url)\nlet filePath = home(fileName)\n\nawait writeFile(filePath, buffer)\n```\n\n[Open download-a-file in Script Kit](https://scriptkit.com/api/new?name=download-a-file&url=https://gist.githubusercontent.com/johnlindquist/b10dbc2218d7c229fd4ed9865739b46f/raw/b9108056d761cdf6b1e8ec5c7d218d11f4002e56/download-a-file.js\")\n","extension":".md","dir":"docs","file":"download-files","description":"","tag":"","section":"Files and Data","i":"2","sectionIndex":"3","createdAt":"2022-06-30T02:17:09Z"},{"name":"Create a Text File","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/796","url":"https://gist.githubusercontent.com/johnlindquist/24794c9b9bfce36ff898d34019555012/raw/c222c46aefd322ae50c9f5fc9e70ba0d2ef74d26/create-a-text-file.js","title":"Create Files","command":"create-files","content":"\n\n\n\n\n \n\n\n## Create a Text File\n\n```js\n// Name: Create a Text File\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = await path() // Select a path that doesn't exist\n\nlet exists = await pathExists(filePath)\n\nif (!exists) {\n await writeFile(filePath, \"Hello world\")\n} else {\n await div(md(`${filePath} already exists...`))\n}\n```\n\n[Open create-a-text-file in Script Kit](https://scriptkit.com/api/new?name=create-a-text-file&url=https://gist.githubusercontent.com/johnlindquist/24794c9b9bfce36ff898d34019555012/raw/c222c46aefd322ae50c9f5fc9e70ba0d2ef74d26/create-a-text-file.js\")\n\n## Update a Text File\n\n```js\n// Name: Update a Text File\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = home(`my-notes.md`)\n\n// `ensureReadFile` will create the file with the content\n// if it doesn't exist\nlet content = await ensureReadFile(filePath, \"Hello world\")\n\nawait editor({\n value: content,\n onInput: _.debounce(async input => {\n await writeFile(filePath, input)\n }, 200),\n})\n```\n\n[Open update-a-text-file in Script Kit](https://scriptkit.com/api/new?name=update-a-text-file&url=https://gist.githubusercontent.com/johnlindquist/b9aa415d3870b8760c54ca57ccabd77d/raw/d3a4645c645dfb0d1749f1719aee817f723357fc/update-a-text-file.js\")\n","extension":".md","dir":"docs","file":"create-files","description":"","tag":"","section":"Files and Data","i":"1","sectionIndex":"3","createdAt":"2022-06-30T02:17:06Z"},{"name":"Editor Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/794","url":"https://gist.githubusercontent.com/johnlindquist/3be99f494f84cea0b21aa673740c0e2e/raw/64757d3981befaf3ef7b3a0eceadab240cd8c2e2/editor-example.js","title":"Built-in Editor","command":"built-in-editor","content":"\n\n\n\n\n \n\n\n## Built-in Editor\n\n```js\n// Name: Editor Example\n\nimport \"@johnlindquist/kit\"\n\nlet result = await editor({\n footer: `Hit cmd+s to continue...`,\n})\n\nawait div(md(result))\n```\n\n[Open editor-example in Script Kit](https://scriptkit.com/api/new?name=editor-example&url=https://gist.githubusercontent.com/johnlindquist/3be99f494f84cea0b21aa673740c0e2e/raw/64757d3981befaf3ef7b3a0eceadab240cd8c2e2/editor-example.js\")\n\n## Load Text in the Editor\n\n```js\n// Name: Load Text Into the Editor\n\nimport \"@johnlindquist/kit\"\n\nlet { data } = await get(\n `https://raw.githubusercontent.com/johnlindquist/kit/main/README.md`\n)\n\nlet result = await editor({\n value: data,\n // Supports \"css\", \"js\", \"ts\", and \"md\". \"md\" is default. More language support coming in future releases.\n language: \"md\",\n footer: `Hit cmd+s to continue...`,\n})\n\nawait div(md(result))\n```\n\n[Open load-text-into-the-editor in Script Kit](https://scriptkit.com/api/new?name=load-text-into-the-editor&url=https://gist.githubusercontent.com/johnlindquist/69efafa66f1c6aa436b8f8283cc1fbba/raw/7f371e045609d3dbee92999b09eac4839262fc9f/load-text-into-the-editor.js\")\n","extension":".md","dir":"docs","file":"built-in-editor","description":"","tag":"","section":"Essentials","i":"3","sectionIndex":"1","createdAt":"2022-06-30T02:17:05Z"},{"shortcut":"cmd shift j","shortcode":"oi","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/792","url":"","title":"Add a Keyboard Shortcut","command":"add-a-keyboard-shortcut","content":"\n\n\n\n\n \n\n\n## // Shortcut Metadata\n\nUse the `// Shortcut` metadata to add a global keyboard shortcut to any script\n\n```js\n// Shortcut: cmd shift j\n\nimport \"@johnlindquist/kit\"\n\nsay(`You pressed command shift j`)\n```\n\n```js\n// Shortcut: opt i\n\nimport \"@johnlindquist/kit\"\n\nsay(`You pressed option i`)\n```\n\n## // Shortcode Metadata\n\nA shortcode allows you quickly run a script without needing to search for it.\n\nTo trigger a `// Shortcode`, type the string of characters from the main menu, then hit `spacebar`. In this example, you would type `oi` then `spacebar` to run this script:\n\n```js\n// Shortcode: oi\n\nimport \"@johnlindquist/kit\"\n\nsay(`You pressed option i`)\n```\n\n## Quick Submit from Hint\n\nA common pattern from Terminal is to quickly submit a script from a hint. Using a bracket around a single character will submit that character when pressed.\n\n```js\nimport \"@johnlindquist/kit\"\n\nlet value = await arg({\n placeholder: \"Continue?\",\n hint: `Another [y]/[n]`,\n})\n\nif (value === \"y\") {\n say(`You pressed y`)\n} else {\n say(`You pressed n`)\n}\n```\n\n## Quick Submit from Choice\n\nIf you need to provide a little more information to the user, use a choice instead of a hint. This allows you to provide a full value that will be submitted instead of just the single letter.\n\n```js\nimport \"@johnlindquist/kit\"\n\nlet value = await arg(\"Select a food\", [\n {\n name: \"[a]pple\",\n value: \"apple\",\n },\n {\n name: \"[b]anana\",\n value: \"banana\",\n },\n {\n name: \"[c]heese\",\n value: \"cheese\",\n },\n])\n\nawait div(md(value))\n```\n","extension":".md","dir":"docs","file":"add-a-keyboard-shortcut","description":"","tag":"","section":"Script Metadata","i":"0","sectionIndex":"2","createdAt":"2022-06-30T02:17:04Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/785","url":"","title":"Accessibility Permissions for Snippets, Keyboard Actions, and Clipboard History","command":"accessibility-permissions-for-snippets-keyboard-actions-and-clipboard-history","content":"Kit.app requires permission for the following reasons:\r\n* Watch user input to trigger Snippets and Clipboard History\r\n* Send keystrokes to trigger for `setSelectedText`, `getSelectedText`, `keyboard.type` and others\r\n* In the future, recording Macros, mouse actions, and more\r\n\r\n❗️ **You must quit Kit.app and re-open it for changes to take effect.** \r\n\r\n![CleanShot 2022-06-20 at 14 27 00](https://user-images.githubusercontent.com/36073/174673600-59020e49-be04-4786-81f7-5bbe20a9ce6c.png)\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-06-20T20:30:58Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/624","url":"","title":"Submit From Live Data","command":"submit-from-live-data","content":"\r\n\r\n\r\nSome scenarios require `setInterval` or other \"live data\" utils. This means you can't use `await` on the arg/div/textarea/etc because `await` prevents the script from continuing on to start the `setInterval`.\r\n\r\n![CleanShot 2021-11-28 at 08 58 04](https://user-images.githubusercontent.com/36073/143775792-34c1fb15-21b9-4690-b8e2-23e1447f65e5.gif)\r\n\r\nUse the Promise `then` on arg/div/textarea/etc to allow the script to continue to run to the `setInterval`. Inside of the `then` callback, you will have to clear the interval for your script to continue/complete:\r\n\r\n```js\r\nlet intervalId = 0\r\ndiv(md(`Click a value`)).then(async value => {\r\n clearInterval(intervalId)\r\n\r\n await div(md(value))\r\n})\r\n\r\nintervalId = setInterval(() => {\r\n let value = Math.random()\r\n\r\n setPanel(\r\n md(`\r\n [${value}](submit:${value})\r\n `)\r\n )\r\n}, 1000)\r\n```\r\n\r\n","extension":".md","dir":"help","file":"submit-from-live-data","description":"Use Then to Allow setInterval","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-28T16:01:58Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/620","url":"","title":"New Kit Environment (Kenv)","command":"new-kit-environment-kenv","content":"\r\n\r\n# New Kit Environment\r\n\r\nThis command will create a new directory in `~/.kenv/kenvs` that contains a `scripts` directory as a place to keep scripts separate from your main Kit environment. This is usually done when you want to share scripts with the community/your team or organize them by type (scripts for GitHub, scripts for Google, etc).\r\n\r\n## Limitations\r\n\r\nScripts inside of `kenvs` do not support metadata that allow scripts to run automatically:\r\n\r\n- Schedule\r\n- Watch\r\n- Background\r\n- System\r\n\r\nIf you need to run one of these automatically, move/duplicate it to your main Kit Environment. You can do this from the main scripts menu by pressing cmd+k to see the script options.","extension":".md","dir":"new","file":"kenv-create","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-26T14:40:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/616","url":"","title":"Strict Mode","command":"strict-mode","content":"\r\n\r\n\r\n# Strict Mode\r\n\r\n`strict` is enabled by default and it forces the user to pick an item from the list, preventing them from entering their own text.\r\n\r\nWhen you disabled `strict`, if you type something that eliminates the entire list, then hit Enter, the string from the input will be passed back.\r\n\r\n> Note: If the list values are Objects and the user inputs a String, you will need to handle either type being returned\r\n\r\n```js\r\n// If the list is completely filtered, hitting enter does nothing.\r\nlet fruit = await arg(`You can only pick one`, [\r\n `Apple`,\r\n `Banana`,\r\n `Orange`,\r\n])\r\n\r\n// If the list is completely filtered, hitting enter sends whatever\r\n// is currently in the input.\r\nlet fruitOrInput = await arg(\r\n {\r\n placeholder: `Pick a fruit or type anything`,\r\n strict: false,\r\n },\r\n [`Apple`, `Banana`, `Orange`]\r\n)\r\n\r\nawait textarea(`${fruit} and ${fruitOrInput}`)\r\n```","extension":".md","dir":"help","file":"strict-mode","description":"Allow user to input text when list is filtered","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-22T23:16:24Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/605","url":"","title":"Download Latest Docs","command":"download-latest-docs","content":"\r\n\r\n\r\n# Download Latest Docs\r\n\r\nHit Enter to grab the latest docs.json from scriptkit.com. Docs will automatically refresh when you re-open the Docs tab.\r\n\r\n## Docs Constantly Updated\r\n\r\nThe docs are generated from GitHub discussions (just like this one). When I update a GitHub discussion in the \"Docs\" category, discussion triggers a webhook that builds a new docs.json file that can be downloaded by the Kit.app.","extension":".md","dir":"help","file":"download-docs","description":"Grab the latest version of the docs.json","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-19T23:27:31Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/604","url":"","title":"Create Scripts from Docs","command":"create-scripts-from-docs","content":"\r\n\r\n# Helpful Snippets in Docs\r\n\r\nThe [Docs](submit:docs) tab contains many helpful snippets to help you start your scripts. You can click on the links above the snippets to create the script just like the one below:\r\n\r\n```js\r\nlet value = await arg(\"Hello world!\")\r\nawait div(`${value} 🌎`, `p-5 text-5xl flex justify-center items-center`)\r\n```","extension":".md","dir":"new","file":"docs","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-19T23:19:12Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/603","url":"","title":"Settings","command":"settings","content":"\r\n\r\n# Settings\r\n\r\nHit Enter to modify Kit.app settings:\r\n- Toggle the menu bar icon\r\n- Toggle open at login\r\n- Toggle auto-update","extension":".md","dir":"kit","file":"settings","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-19T19:38:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/594","url":"","title":"Real-Time Results from Input","command":"real-time-results-from-input","content":"\r\n\r\n\r\nWhen you pass a function as the second argument of `arg`, you can take the current `input` and return a string. Kit.app will then render the results as HTML. The simplest example looks like this:\r\n\r\n```js\r\nawait arg(\"Start typing\", input => input)\r\n```\r\n\r\nIf you want to make it look a bit nicer, you can wrap the output with some HTML:\r\n\r\n```js\r\nawait arg(\r\n \"Type something\",\r\n input =>\r\n `
\r\n${input || `Waiting for input`}\r\n
`\r\n)\r\n```\r\n\r\nGrowing on the example above, here's a Celsius to Fahrenheit converter:\r\n\r\n```js\r\nlet cToF = celsius => {\r\n return (celsius * 9) / 5 + 32\r\n}\r\n\r\nawait arg(\r\n \"Enter degress in celsius\",\r\n input =>\r\n `
\r\n${input ? cToF(input) + \"f\" : `Waiting for input`}\r\n
`\r\n)\r\n```","extension":".md","dir":"help","file":"real-time","description":"Update output on every keypress","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-15T15:54:11Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/589","url":"","title":"Flags for Choices","command":"flags-for-choices","content":"\r\n\r\n\r\n\r\n# Flags for Choices\r\n\r\nTo add an options menu to your choices, you must provide a `flags` object. If one of the keyboard shortcuts are hit, or the user selects the option, then the `flag` global will have the matching key from your flags set to `true`:\r\n\r\n\r\n```js\r\nlet urls = [\r\n \"https://scriptkit.com\",\r\n \"https://egghead.io\",\r\n \"https://johnlindquist.com\",\r\n]\r\n\r\nlet flags = {\r\n open: {\r\n name: \"Open\",\r\n shortcut: \"cmd+o\",\r\n },\r\n copy: {\r\n name: \"Copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n}\r\n\r\nlet url = await arg(\r\n { placeholder: `Press 'right' to see menu`, flags },\r\n urls\r\n)\r\n\r\nif (flag?.open) {\r\n $`open ${url}`\r\n} else if (flag?.copy) {\r\n copy(url)\r\n} else {\r\n console.log(url)\r\n}\r\n```\r\n\r\nUsing the same script above, In the terminal, you would pass an open flag like so:\r\n\r\n```bash\r\nmy-sites --open\r\n```","extension":".md","dir":"help","file":"flags","description":"How to add a secondary menu to each choice","tag":"arg","section":"","i":"","sectionIndex":"","createdAt":"2021-11-13T04:46:39Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/588","url":"","title":"Run Scripts from Other Apps","command":"run-scripts-from-other-apps","content":"\r\n\r\n# Run Scripts from Other Apps\r\n\r\nAre you a fan of?\r\n- [Keyboard Maestro](https://www.keyboardmaestro.com/main/)\r\n- [Better Touch Tool](https://folivora.ai/)\r\n- [Karabiner](https://karabiner-elements.pqrs.org/)\r\n- [Raycast](https://www.raycast.com/)\r\n- [Alfred](https://www.alfredapp.com/)\r\n\r\nWe love all these tools! So we made sure the scripts you create in Script Kit can be invoked by them too:\r\n\r\nIf you have a script named `center-app`, then you can paste the following snippet into the \"scripts\" section of any of these tools.\r\n\r\n```bash\r\n~/.kit/kar center-app\r\n```\r\n\r\n`kar` is an executable that takes the script name and sends it to Kit.app to run.\r\n\r\n> It's named `kar` because we're HUGE fans of [karabiner](https://karabiner-elements.pqrs.org/) and using \"kit kar\" as a transport\r\n> for scripts into the app makes us giggle 😇\r\n\r\nAny arguments you pass to the script will also be sent along. So if you want to run `center-app` with a padding of `50`:\r\n\r\n```bash\r\n~/.kit/kar center-app 50\r\n```","extension":".md","dir":"help","file":"other-apps","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T22:26:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/586","url":"","title":"Selecting Files","command":"selecting-files","content":"\r\n\r\n\r\n# Selecting Files\r\n\r\nYou can get files into your scripts in many different ways.\r\n\r\n## Currently Selected Files\r\n\r\nOne of the most convenient is to just run a script on the currently selected files in Finder:\r\n\r\n```js\r\n// Always returns a string\r\nlet filePath = await getSelectedFile()\r\n// If multiple files selected, split by \"\\n\"\r\nif (filePath.includes(\"\\n\")) {\r\n let filePaths = filePath.split(\"\\n\")\r\n}\r\n```\r\n\r\n## Prompt for Selection\r\n\r\nOtherwise, you can prompt to select a file:\r\n\r\n```js\r\n// Open the Finder prompt for file selection\r\nlet filePath = await selectFile()\r\n```\r\n\r\n```js\r\n// Open the Finder prompt for folder selection\r\nlet folderPath = await selectFolder()\r\n```\r\n\r\n## Drag and Drop\r\n\r\n```js\r\n\r\n// Kit.app opens a UI where you can drop files/text/links\r\n// Dropping files will result in an Array of files object\r\nlet dropResult = await drop()\r\ndevTools(dropResult)\r\n```\r\n","extension":".md","dir":"help","file":"selecting-files","description":"selectFile, selectFolder, drop","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T16:57:09Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/582","url":"","title":"The Read/Edit/Write Loop","command":"the-readeditwrite-loop","content":"\r\n\r\nIf want to quickly jot down notes or just edit text files, use the read, edit, write loop:\r\n\r\n```js\r\nlet filePath = tmpPath(\"notes.md\") //tmpPath is a temp dir based on the script name\r\n\r\nlet contents = await ensureReadFile(filePath)\r\n\r\nwhile (true) {\r\n // `editor` will be paywalled in the future. Use `textarea` if you don't want to support us 😢\r\n contents = await editor(contents) // hit cmd+s to \"submit\" and continue back in the loop. cmd+w to close.\r\n await writeFile(filePath, contents)\r\n}\r\n\r\n```","extension":".md","dir":"help","file":"read-edit-write","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T04:58:24Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/581","url":"","title":"Spawn and Position a Window","command":"spawn-and-position-a-window","content":"\r\n\r\n\r\n# Spawn and Position a Window\r\n\r\nYou can control the size/position of each `show` window you create, but you'll need some info from the current screen (especially with a multi-monitor setup!) to be able to position the window where you want it:\r\n\r\n```js\r\nlet width = 480\r\nlet height = 320\r\n\r\nlet { workArea } = await getActiveScreen()\r\nlet { x, y, width: workAreaWidth } = workArea\r\n\r\nshow(\r\n md(`\r\n# I'm in the top right of the current screen!\r\n\r\n
\r\n😘\r\n
\r\n`),\r\n {\r\n width,\r\n height,\r\n x: x + workAreaWidth - width,\r\n y: y,\r\n }\r\n)\r\n```","extension":".md","dir":"help","file":"spawn-and-position","description":"show, getActiveScreen","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T04:44:55Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/580","url":"","title":"Quickly Clone Project Templates","command":"quickly-clone-project-templates","content":"\r\n\r\n# Quickly Clone Project Templates\r\n\r\nWe're developers. We clone project templates from github. [degit](https://www.npmjs.com/package/degit) is available on the global scope for exactly this scenario.\r\n\r\n```js\r\nlet projectName = await arg(\"Name your project\")\r\nlet targetDir = home(\"projects\", projectName)\r\n\r\nawait degit(`https://github.com/sveltejs/template`).clone(\r\n targetDir\r\n)\r\n\r\nedit(targetDir)\r\n```","extension":".md","dir":"help","file":"degit","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T04:06:48Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/579","url":"","title":"Downloading Files","command":"downloading-files","content":"\r\n\r\n\r\n# Download Files\r\n\r\nThe [download](https://www.npmjs.com/package/download) npm package is exposed globally for you to easily download files:\r\n\r\n```js\r\nlet dest = tmpPath() //tmp path is a path generated based on the script name\r\n\r\nawait download(\r\n `https://johnlindquist.com/images/logo/john@2x.png`,\r\n dest\r\n)\r\n\r\n// open the dir in finder to check out your newly downloaded file\r\nawait $`open ${dest}`\r\n```","extension":".md","dir":"help","file":"downloading-files","description":"download","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T02:36:14Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/562","url":"","title":"Quick Keys","command":"quick-keys","content":"\r\n\r\n# Quick Keys\r\n\r\nA quick key allows you to bind a single key to submit a prompt.\r\n\r\nYou can add quick keys inside the \"hint\" if you don't want to bother with choices:\r\n\r\n```js\r\n//Type \"y\" or \"n\"\r\nlet confirm = await arg({\r\n placeholder: \"Eat a taco?\",\r\n hint: `[y]es/[n]o`,\r\n})\r\n\r\nconsole.log(confirm) //\"y\" or \"n\"\r\n```\r\n\r\nOtherwise, add the quick keys in the `name` of the choices and it will return the quick key:\r\n\r\n```js\r\n // Type \"a\", \"b\", or \"g\"\r\nlet fruit = await arg(`Pick one`, [\r\n `An [a]pple`,\r\n `A [b]anana`,\r\n `a [g]rape`,\r\n])\r\n\r\nconsole.log(fruit) //\"a\", \"b\", or \"g\"\r\n```\r\n\r\nYou can add a value, then typing the quick key will return the value:\r\n\r\n```js\r\n// Type \"c\" or \"a\"\r\nlet vegetable = await arg(\"Pick a veggie\", [\r\n { name: \"[C]elery\", value: \"Celery\" },\r\n { name: \"C[a]rrot\", value: \"Carrot\" },\r\n])\r\n\r\nconsole.log(vegetable) //\"Celery\" or \"Carrot\"\r\n```","extension":".md","dir":"help","file":"quick-keys","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-09T03:10:10Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/555","url":"","title":"Creating Previews","command":"creating-previews","content":"\r\n\r\n\r\n# Creating Previews\r\n\r\nEach choice in an `arg` can have an associated `preview`. Previews gracefully enhance from a string all the way up to multiple async functions that return strings based on choice.\r\n\r\nYou can toggle the preview pane open and closed with cmd+P\r\n\r\n\r\n```js\r\n// Just a string\r\nawait arg(\r\n \"Select a fruit\",\r\n md(`I recommend typing \"Apple\"`) // \"md\" converts strings to HTML\r\n)\r\n```\r\n\r\n```js\r\n// A function, takes typed \"input\", returns string\r\nawait arg(\"Select a fruit\", input =>\r\n md(`You typed \"${input}\"`)\r\n)\r\n```\r\n\r\n```js\r\n// An async function, takes typed \"input\", returns string\r\n// `hightlight` requires \"async\" takes markdown, applies code highlighting\r\n\r\nawait arg(\r\n \"Select a fruit\",\r\n async input =>\r\n await highlight(` \r\n~~~js\r\nawait arg(\"${input}\")\r\n~~~\r\n `)\r\n)\r\n```\r\n\r\n```js\r\n// A \"preview\" per choice\r\nawait arg(\"Select a fruit\", [\r\n { name: \"Apple\", preview: `Apple, yum! 🍎` },\r\n { name: \"Banana\", preview: `Banana, yum too! 🍌` },\r\n])\r\n```\r\n\r\n```js\r\n// Async \"preview\" per choice\r\nlet preview = async ({ name, input }) =>\r\n await highlight(`\r\n~~~js\r\n// ${name}\r\nawait arg(\"${input}!\")\r\n~~~\r\n`)\r\n```\r\n\r\n```js\r\n//\"input\" param is required to switch prompt mode from \"filter list\" to \"generate list\"\r\nawait arg(\"Select a fruit\", async input => {\r\n return [\r\n { name: `Apple ${input}`, preview },\r\n { name: `Banana ${input}`, preview },\r\n ]\r\n})\r\n```\r\n\r\n```js\r\n// Static preview with static choices\r\nawait arg(\r\n {\r\n preview: md(`\r\n# Pick a fruit\r\n\r\n\r\n `),\r\n },\r\n [\"Apple\", \"Banana\", \"Orange\"]\r\n)\r\n```\r\n\r\n```js\r\n// Dynamic choices, static preview\r\nawait arg(\r\n {\r\n preview: async () =>\r\n await highlight(`\r\n## This is just information\r\n\r\nUsually to help you make a choice\r\n \r\nJust type some text to see the choices update\r\n`),\r\n },\r\n async input => {\r\n return Array.from({ length: 10 }).map(\r\n (_, i) => `${input} ${i}`\r\n )\r\n }\r\n)\r\n```","extension":".md","dir":"help","file":"preview","description":"arg, preview","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-06T15:55:41Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/548","url":"","title":"Check for Updates","command":"check-for-updates","content":"\n \n# Check for Updates\n\nKit.app will check for updates each time your machine wakes from sleep. But if you heard about an update and just can't wait, trigger this command to grab it.\n","extension":".md","dir":"kit","file":"update","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:37Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/547","url":"","title":"Toggle Tray Icon","command":"toggle-tray-icon","content":"\n \n# Toggle Tray Icon\n\nToggle if the system tray icon is visible\n","extension":".md","dir":"kit","file":"toggle-tray","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:36Z"},{"system":"unlock-screen","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/546","url":"","title":"View System Event Scripts","command":"view-system-event-scripts","content":"\n \n# View System Event Scripts\n\nThis menu shows scripts that run on system events.\n\nAdd the `System` metadata to run your script on a system event\n\n```js\n// System: unlock-screen\n```\n\nAvailable events:\n\n- suspend\n- resume\n- on-ac\n- on-battery\n- shutdown\n- lock-screen\n- unlock-screen\n- user-did-become-active\n- user-did-resign-active\n- Read about the available events [here](https://www.electronjs.org/docs/latest/api/power-monitor#events)\n\n> Note: YMMV based on your specific machine setup.\n","extension":".md","dir":"kit","file":"system-events","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/545","url":"","title":"Sync $PATH from Terminal to Kit.app","command":"sync-dollarpath-from-terminal-to-kitapp","content":"\n \n# Sync $PATH from Terminal to Kit.app\n\nHave a command that's working in your terminal, but doesn't work when you call it with Script Kit?\n\nUse this to sync up your \"PATH\" from your terminal to the \"PATH\" that Script Kit will use.\n\nYou can manually edit a `PATH` value any time in `~/.kenv/.env`\n","extension":".md","dir":"kit","file":"sync-path-instructions","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:35Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/544","url":"","title":"Switch to TypeScript Mode","command":"switch-to-typescript-mode","content":"\n \n# Switch to TypeScript Mode\n\nPrefer TypeScript for your scripts? Hit _Enter_!\n","extension":".md","dir":"kit","file":"switch-to-ts","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:34Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/543","url":"","title":"Switch to JavaScript Mode","command":"switch-to-javascript-mode","content":"\n \n# Switch to JavaScript Mode\n\nPrefer JavaScript for your scripts? Hit _Enter_!\n","extension":".md","dir":"kit","file":"switch-to-js","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/542","url":"","title":"Prepare Script for Stream Deck","command":"prepare-script-for-stream-deck","content":"\n \n# Prepare Script for Stream Deck\n\nWant to map a script to your Stream Deck buttons? Use this to walk you through the process!\n","extension":".md","dir":"kit","file":"stream-deck","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:33Z"},{"schedule":"*/10 * * * * *","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/541","url":"","title":"View Schedule","command":"view-schedule","content":"\n \n# View Schedule\n\nList all of the scheduled scripts and see the next time time they'll run.\n\nSchedule a script to run on a cron with the `Schedule` metadata:\n\n```js\n// Schedule: */10 * * * * *\n```\n\nUse [https://crontab.guru/](https://crontab.guru/) to easily generate cron syntax.\n","extension":".md","dir":"kit","file":"schedule","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:32Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/540","url":"","title":"Quit","command":"quit","content":"\n \n# Quit\n\nSee you soon! 👋\n","extension":".md","dir":"kit","file":"quit","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:31Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/539","url":"","title":"Open Script Kit at Login","command":"open-script-kit-at-login","content":"\n \n# Open Script Kit at Login\n\nToggle if Script Kit opens when you log on\n","extension":".md","dir":"kit","file":"open-at-login","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:30Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/538","url":"","title":"Manage npm Packages","command":"manage-npm-packages","content":"\n \n# Manage npm Packages\n\nThis will help you install/uninstall packages from your `~/.kenv/node_modules`\n\n> Note: You can use the `npm` method in your script to prompt the user to auto-install:\n\n```js\nlet express = await npm(\"express\")\n```\n","extension":".md","dir":"kit","file":"manage-npm","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:30Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/537","url":"","title":"Open kit.log","command":"open-kitlog","content":"\n \n# Open kit.log\n\nView the Kit.app log at `~/.kit/logs/kit.log`\n","extension":".md","dir":"kit","file":"kit-log","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:29Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/536","url":"","title":"Clear Kit Prompt Cache","command":"clear-kit-prompt-cache","content":"\n \n# Clear Kit Prompt Cache\n\nEach time you move or resize the prompt around for your scripts, Script Kit will store the position and size. If you want to reset the position of you prompts back to the centered defaults, then run this command.\n","extension":".md","dir":"kit","file":"kit-clear-prompt","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:28Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/535","url":"","title":"Manage Kit Environments","command":"manage-kit-environments","content":"\n \n# Manage Kit Environments\n\nA \"kenv\" (Kit Environment) is a directory with a `scripts` directory. This is the place to create a kenv to manage scripts for your dev team or projects that expose APIs (GitHub, Vercel, etc). We'll be releasing official Script Kit kenvs in the future to show off some of the best practices. 👍\n\nClone, create new, link, push, pull, remove. This menu allows you to manage any of the kenvs you've added to your machine.\n","extension":".md","dir":"kit","file":"kenv-manage","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/534","url":"","title":"Credits","command":"credits","content":"\n \n# Credits\n\n## John Lindquist\n\nDevelopment\n\n- [https://johnlindquist.com](https://johnlindquist.com)\n- [@johnlindquist](https://twitter.com/johnlindquist)\n\n## Vojta Holik\n\nDesign\n\n- [https://vojta.io/](https://vojta.io/)\n- [@vjthlk](https://twitter.com/vjthlk)\n\n## Supported By\n\n- [egghead.io](https://egghead.io)\n","extension":".md","dir":"kit","file":"credits","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/533","url":"","title":"Generate bin Files","command":"generate-bin-files","content":"\n \n# Generate bin Files\n\nIf you manually manage files in the `scripts` dir (instead of using Kit.app or the `kit` CLI) you may run into the scenarios where you have to re-generate all the `bin` executables. This will do that for you.\n","extension":".md","dir":"kit","file":"create-all-bins","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:26Z"},{"shortcut":"cmd option g","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/532","url":"","title":"Add/Change a Script Shortcut","command":"addchange-a-script-shortcut","content":"\n \n# Add/Change a Script Shortcut\n\nThis list all the scripts and allows you to add a shortcut to it.\n\nYou can manually add shortcuts to scripts like so:\n\n```js\n// Shortcut: cmd option g\n```\n\nThis menu will manage that for you\n","extension":".md","dir":"kit","file":"change-shortcut","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:25Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/531","url":"","title":"Change Script Kit Shortcut","command":"change-script-kit-shortcut","content":"\n \n# Change Script Kit Shortcut\n\nDon't like `cmd+;`? Change it here!\n","extension":".md","dir":"kit","file":"change-main-shortcut","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:25Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/530","url":"","title":"Change Editor","command":"change-editor","content":"\n \n# Change Editor\n\nThis will re-prompt you to pick an editor from your PATH by updating your kenv `.env`.\n\nYou can always manually change the editor that Script Kit uses to open files in `~/.kenv/.env`.\n\nThe following would use `code` (assuming is on the \"PATH\").\n\n```bash\nKIT_EDITOR=code\n```\n\nIf `code` isn't on your PATH, you can add the full path to the editor.\n","extension":".md","dir":"kit","file":"change-editor","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:24Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/529","url":"","title":"Add ~/.kit/bin to $PATH","command":"add-kitbin-to-dollarpath","content":"\r\n\r\n\r\n \r\n# Add ~/.kit/bin to $PATH\r\n\r\n> This is similar to VS Code's \"Add `code` to path\"\r\n\r\nYou can run the `kit` CLI from your terminal with\r\n\r\n```bash\r\n~/.kit/bin/kit\r\n```\r\n\r\nbut this option will allow you run the CLI with:\r\n\r\n```bash\r\nkit\r\n```\r\n\r\n> If you're familiar with adding to your `.zshrc`, just add `~/.kit/bin` to your PATH.\r\n\r\nThe `kit` CLI will allow you to run, edit, etc scripts from your terminal.","extension":".md","dir":"kit","file":"add-kit-to-profile","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:23Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/528","url":"","title":"Add ~/.kenv/bin to $PATH","command":"add-kenvbin-to-dollarpath","content":"\n \n# Add ~/.kenv/bin to $PATH\n\nEach time you create a script, Script Kit also generates a command based on the name you can run from the terminal.\n\nIf you create a script named `list-downloads`, then Script Kit creates a `~/.kenv/bin/list-downloads` executable.\n\nThen you can run the command like so in the terminal:\n\n```bash\n~/.kenv/bin/list-downloads\n```\n\nThis will walk you through running the command without the full path:\n\n```bash\nlist-downloads\n```\n","extension":".md","dir":"kit","file":"add-kenv-to-profile","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:23Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/527","url":"","title":"User Input","command":"user-input","content":"\r\n\r\n \r\n# User Input\r\n\r\nReceive text from a user by adding `arg` to your script:\r\n\r\n```js\r\nlet value = await arg()\r\n```\r\n\r\n`arg` will prompt the user to enter text, wait for the text, then return the value of the text to `value` and continue on with the script.\r\nYou can then use `value` as a string in your script however you want.\r\n\r\nIf you want to tell the user what information the prompt expects, provide a string:\r\n\r\n```js\r\nawait arg(\"Please enter your name\")\r\n```\r\n\r\nIf you want give the user options to select, provide an array as the second argument:\r\n\r\n```js\r\nawait arg(\"Select a fruit:\", [\"apple\", \"banana\", \"grape\"])\r\n```\r\n\r\n## Drag and drop\r\n\r\nPrompt the user to drag and drop a file by using the `drop` method:\r\n\r\n```js\r\nlet filePath = await drop()\r\n```\r\n\r\n## Longer Text\r\n\r\nAllow the user to input multiple lines of text using `textarea`:\r\n\r\n```js\r\nlet text = await textarea()\r\n```\r\n\r\nPre-load the `textarea` with text by passing a string:\r\n\r\n```js\r\nlet pleaseEditMe = `Some text I want to edit`\r\nlet text = await textarea(pleaseEditMe)\r\n```\r\n\r\n## Code Editor\r\n\r\n(💵 In the future, using `editor` will require a paid update)\r\n\r\nLaunch a full code editor using `editor`. This is great\r\n\r\n```js\r\nlet text = await editor()\r\n```\r\n\r\nPre-load the `editor` with text and specify a language for code highlighting/features:\r\n\r\n```js\r\nlet text = `\r\n# My Markdown\r\n\r\n* one\r\n* two\r\n`\r\nlet editedText = await editor(text, \"markdown\")\r\nawait div(md(editedText))\r\n```\r\n\r\n## Keyboard Shortcut\r\n\r\n```js\r\nlet keyData = await hotkey()\r\n```\r\n\r\nIf you were to type `cmd+j`, `keyData` would give you the following response:\r\n\r\n```json\r\n{\r\n \"key\": \"j\",\r\n \"command\": true,\r\n \"shift\": false,\r\n \"option\": false,\r\n \"control\": false,\r\n \"fn\": false,\r\n \"hyper\": false,\r\n \"os\": false,\r\n \"super\": false,\r\n \"win\": false,\r\n \"shortcut\": \"command j\"\r\n}\r\n```\r\n","extension":".md","dir":"help","file":"user-input","description":"arg, drop, hotkey, editor, textarea","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:22Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/526","url":"","title":"Run Shell Commands","command":"run-shell-commands","content":"\n \n# Run Shell Commands\n\nScript Kit bundles [zx](https://github.com/google/zx) as the global `$`\n\nExample from their docs (make sure to `cd` to the proper dir)\n\n```js\nawait $`cat package.json | grep name`\n\nlet branch = await $`git branch --show-current`\nawait $`dep deploy --branch=${branch}`\n\nawait Promise.all([\n $`sleep 1; echo 1`,\n $`sleep 2; echo 2`,\n $`sleep 3; echo 3`,\n])\n\nlet name = \"foo bar\"\nawait $`mkdir /tmp/${name}`\n```\n","extension":".md","dir":"help","file":"terminal-app","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:21Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/525","url":"","title":"Store Data","command":"store-data","content":"\n\n# Store Data\n\n## Read/Write to a `.env` file\n\nUse `await env(\"SOME_KEY\")` to check if the value exists in the `~/.kenv/.env` file, if not, prompt the user to enter it and store it for the next time the script is run\n\n```js\nlet value = await env(\"MY_KEY\")\n```\n\n## Read/Write Data to a json file `db`\n\nThe `db` method will create a json file to store values for you in `~/.kenv/db`.\n\n```js\nlet data = await db({ values: [] })\n\nlet value = await arg(\"Type something\")\n\ndata.values.push(value)\nawait data.write()\n```\n","extension":".md","dir":"help","file":"store-data","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:20Z"},{"shortcut":"option+g","shortcode":"g","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/524","url":"","title":"Keyboard Shortcuts","command":"keyboard-shortcuts","content":"\n \n# Keyboard Shortcuts\n\n## Global Shortcuts\n\nAdd a global shortcut to any script by adding the `//Shortcut: ` metadata:\n\n```js\n// Shortcut: option+g\n```\n\nIn the `Run` tab, use the script options menu `>` to change a shortcut. (Hit `cmd+k` to toggle to the options menu)\nIn the `Kit` tab, you can run `Change script shortcut` to list all script with shortcuts and change them from there.\n\n## Shortcodes\n\nIf you have a script you run often, you can also use \"shortcodes\" in the app. Add the following to the top of your script:\n\n```js\n// Shortcode: g\n```\n\nNow, when you launch the app, type `g` then hit `space` to run the script.\n\nThe main menu (`Run New Kit Help Hot`) also uses shortcodes, so typing `h` then space will switch to the `Help` tab. Or `n` then space switches to the `New` tab.\n","extension":".md","dir":"help","file":"shortcuts","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:20Z"},{"schedule":"*/10 * * * * *","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/523","url":"","title":"Schedule a Script","command":"schedule-a-script","content":"\n \n# Schedule a Script\n\nUse cron syntax to run scripts on a schedule:\n\n```js\n// Schedule: */10 * * * * *\n```\n\n> Note: these scripts must not include `arg` or they will time out after 10 seconds\n","extension":".md","dir":"help","file":"schedule","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/522","url":"","title":"Subscribe to the Newsletter","command":"subscribe-to-the-newsletter","content":"\n \n# Subscribe to the Newsletter\n\n- Featured Scripts\n- Latest Updates\n- Tutorials and lessons\n- Script Kit Tips and Tricks\n- Curated dev news\n","extension":".md","dir":"help","file":"join","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/521","url":"","title":"Get Help on Github Discussions","command":"get-help-on-github-discussions","content":"\n \n# Get Help on Github Discussions\n\nThe Script Kit community lives on GitHub discussions.\n\nThis is the place to:\n\n- 🥰 [Share scripts](https://github.com/johnlindquist/kit/discussions/categories/share)\n- 🙏 [Ask questions](https://github.com/johnlindquist/kit/discussions/categories/q-a)\n- 💡 [Discuss ideas](https://github.com/johnlindquist/kit/discussions/categories/ideas)\n- 😱 [Report errors](https://github.com/johnlindquist/kit/discussions/categories/error)\n\nOr just hit _Enter_ to browse all.\n\n> We'll do our best to respond ASAP!\n","extension":".md","dir":"help","file":"get-help","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:18Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/520","url":"","title":"Reading/Writing Files","command":"readingwriting-files","content":"\n \n# Reading/Writing Files\n\nMost of the methods from [fs/promises](https://nodejs.org/api/fs.html#promises-api) and [fs-extra](https://www.npmjs.com/package/fs-extra) are globally available\n\n## Create a File\n\n```js\n// \"home\" is a method that wraps `path.resolve` based on your home directory\nlet filePath = home(\"projects\", \"kit\", \"note.txt\")\n// writes a file to the filePath using `fs-extra's` \"outputFile\"\nawait outputFile(filePath, `Drink more water`)\n```\n\n## Select and Edit a File\n\n```js\n// \"selectFile\" uses Finder's file selector\nlet filePath = await selectFile()\nlet contents = await readFile(filePath, \"utf-8\")\n\n// Pass the text contents into the editor to quickly edit a file\nlet result = await editor(contents)\nawait writeFile(filePath, result)\n```\n","extension":".md","dir":"help","file":"files","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:17Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/519","url":"","title":"FAQ","command":"faq","content":"\n \n# FAQ\n\n## What is Script Kit?\n\nScript Kit is an open-source dev tool for creating, running, editing, and sharing scripts.\n\nThese scripts can run in the Kit.app, the Terminal, GitHub actions, package.json scripts, webhooks, or pretty much anywhere.\n\n## Community of Scripters?\n\nThe main goal of Script Kit is to build a community of people who love to script away the frictions of their day! 🥰\n\n## What are Kit.app, kit, and kenvs?\n\n- Kit.app - The Kit.app provides a UI for your scripts. The app is \"script-driven\" meaning that every time you launch the app, you're really launching a script. The main menu, even though complex, is a script you could write.\n\n- kit - \"kit\" is the sdk of Script Kit\n\n - A bundle of JavaScript common libs wrapped by an API to make writing scripts easier (`get`, `download`, `replace`, `outputFile`, etc)\n - APIs for interacting with your OS (`edit`, `focusTab`, `say`, `notify`, etc)\n - APIs for interacting with Kit.app and Terminal (`arg`, `env`, etc)\n - Scripts and utils for app setup, managing kenvs, parsing scripts, etc\n\n- kenvs - Kit Enviroments (AKA \"kenv\") are directories that contain a \"scripts\" directory. If you point \"kit\" at a \"kenv\", kit will parse the scripts and give you tools to simplify running and managing them.\n","extension":".md","dir":"help","file":"faq","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:17Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/518","url":"","title":"Display Data","command":"display-data","content":"\r\n\r\n\r\n# Display Data\r\n \r\n## Display HTML\r\n\r\nUse the `div` method to display html.\r\n\r\n```js\r\nawait div(`
Hi
`)\r\n```\r\n\r\n### Add Padding\r\n\r\nThe second argument of `div` allows you to add [tailwind](https://tailwindcss.com/) classes to the container of your html. For example, `p-5` will add a `padding: 1.25rem;` to the container.\r\n\r\n```js\r\nawait div(`
Hi
`, `p-5`)\r\n```\r\n\r\n## Display Markdown\r\n\r\nPass a string of markdown to the `md` method. This will convert the markdown to html which you can then pass to the `div`\r\n\r\n```js\r\nlet html = md(`\r\n# Hi\r\n`)\r\nawait div(html)\r\n```\r\n\r\nIf you want to highlight your markdown, pass the markdown string to the `await highlight()` method:\r\n\r\n```js\r\nlet html = await highlight(`\r\n# Hi\r\n`)\r\nawait div(html)\r\n```\r\n\r\n## Create a New Window\r\n\r\nUse the `show` method to spawn a new, persisting window that is disconnected from the script.\r\n\r\nMuch future work will be put into components/widgets that can run outside of the main app UI, but for now, it simply display any html you send to it:\r\n\r\n```js\r\nshow(`\r\n
\r\n Hello there!\r\n
\r\n\r\n`)\r\n```\r\n","extension":".md","dir":"help","file":"display-data","description":"div, show","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:16Z"},{"author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/517","url":"","title":"Take Credit for Your Work","command":"take-credit-for-your-work","content":"\n \n# Take Credit for Your Work\n\nAdd the `Author` and `Twitter` metadata to get credited in the UI and on [scriptkit.com](https://scriptkit.com) for your script:\n\n```js\n// Author: John Lindquist\n// Twitter: @johnlindquist\n```\n","extension":".md","dir":"help","file":"credit","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:15Z"},{"author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/515","url":"","title":"Creating Scripts","command":"creating-scripts","content":"\r\n \r\n# Creating Scripts\r\n\r\n## Pro-tips\r\n\r\n1. The fastest way to create a script: Launch the app as per usual, then type the name of a script that doesn't exist:\r\n\r\n```bash\r\nMy New Script\r\n```\r\n\r\nThen hit _Enter_.\r\n\r\n2. You can use TypeScript by opening the `Kit` tab and \"Switch to TypeScript Mode\"\r\n\r\n3. The files in the `~/.kenv/templates` dir will be used when creating new scripts. You can create a template with your personal data filled out `~/.kenv/templates/john.js` then in your `.env`, set `KIT_TEMPLATE=john`\r\n\r\n```js\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n```\r\n\r\n## Why the Import?\r\n\r\nThe line is _not_ required, but this comment helps code editors to apply the correct type definition files for autocomplete/code-hinting.\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n```","extension":".md","dir":"new","file":"new","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:14Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/514","url":"","title":"Create a Script from a Gist","command":"create-a-script-from-a-gist","content":"\n \n# Create a Script from a Gist\n\nIn the `Run` tab, with a script selected, hit `cmd+g` (think \"g\" for \"gist\").\n\nThis will create a gist then copy gist's url to the clipboard.\n\nYou can share this url with a friend, then they can copy/paste it into this `New from url` command to quickly grab it.\n","extension":".md","dir":"new","file":"new-from-url","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:13Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/513","url":"","title":"Get Featured","command":"get-featured","content":"\n \n# Get Featured\n\nSimply share your script to Script Kit's [GitHub Discussions](https://github.com/johnlindquist/kit/discussions/categories/share) in the \"share\" category and it will automatically (after a review period) get added to [scriptkit.com](https://scriptkit.com)\n\nYou'll even have a user page based on your GitHub user name: [scriptkit.com/johnlindquist](https://scriptkit.com/johnlindquist)\n\n## Prep a Script for Discussion\n\nWith a script selected, hit `cmd+s` (think \"s\" for \"share\"). This automatically:\n\n- Creates a gist of your script\n- Creates the install link\n- Copies the script and link to your clipboard\n- Opens the discussions page\n\nSo all you have to do is \"paste\"!\n","extension":".md","dir":"new","file":"browse-examples","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:13Z"}]
+[{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/826","url":"","title":"Run From the Terminal","command":"run-from-the-terminal","content":"\n\n\n\n\n \n\n\nIn progress...","extension":".md","dir":"docs","file":"run-from-the-terminal","description":"","tag":"","section":"Advanced","i":"1","sectionIndex":"5","createdAt":"2022-07-06T17:51:54Z"},{"name":"Get Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/825","url":"","title":"Get, Post, Put, Delete Requests","command":"get-post-put-delete-requests","content":"\n\n\n\n\n \n\n\nThe `get`, `post`, `put`, and `delete` methods use the [axios](https://www.npmjs.com/package/axios) API\n\n## Make a Get Request\n\n```js\n// Name: Get Example\n\nimport \"@johnlindquist/kit\"\n\nlet response = await get(\n \"https://scriptkit.com/api/get-example\"\n)\n\nawait div(md(response.data.message))\n```\n\n[Open get-example in Script Kit](https://scriptkit.com/api/new?name=get-example&url=https://gist.githubusercontent.com/johnlindquist/cdef2447a1b49ad163a9c696369c930d/raw/65407970a7dd1af406b0cbee94e876e84822e16e/get-example.js\")\n\n## Make a Post Request\n\n```js\n// Name: Post Example\n\nimport \"@johnlindquist/kit\"\n\nlet response = await post(\n \"https://scriptkit.com/api/post-example\",\n {\n name: await arg(\"Enter your name\"),\n }\n)\n\nawait div(md(response.data.message))\n```\n\n[Open post-example in Script Kit](https://scriptkit.com/api/new?name=post-example&url=https://gist.githubusercontent.com/johnlindquist/8bac8a4e303fe21c93b93787c828419f/raw/fcb26d4eafa0fa36fcb88b02716e4503e130b375/post-example.js\")\n","extension":".md","dir":"docs","file":"get-post-put-delete-requests","description":"","tag":"","section":"Files and Data","i":"3","sectionIndex":"3","createdAt":"2022-07-06T17:51:54Z"},{"menu":"Database Read/Write Example","description":"","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/823","url":"","title":"Simple Storage","command":"simple-storage","content":"\n\n\n\n\n \n\n\n## Read and Write Using the `db` Helper\n\nThe `db` helpers reads/writes to json files in the `~/.kenv/db` directory. It's meant as a simple wrapper around common json operations.\n\n```js\n// Menu: Database Read/Write Example\n// Description: Add/remove items from a list of fruit\n\nlet fruitDb = await db([\"apple\", \"banana\", \"orange\"])\n\n// This will keep prompting until you hit Escape\nwhile (true) {\n let fruitToAdd = await arg(\n {\n placeholder: \"Add a fruit\",\n //allows to submit input not in the list\n strict: false,\n },\n fruitDb.items\n )\n\n fruitDb.items.push(fruitToAdd)\n await fruitDb.write()\n\n let fruitToDelete = await arg(\n \"Delete a fruit\",\n fruitDb.items\n )\n\n fruitDb.items = fruitDb.items.filter(\n fruit => fruit !== fruitToDelete\n )\n\n await fruitDb.write()\n}\n```\n\n[Open db-store in Script Kit](https://scriptkit.com/api/new?name=db-store&url=https://gist.githubusercontent.com/johnlindquist/a7cda43e196f6b6e38e4c66cba8cdb74/raw/8d93dc14970bac042763cb86b30456b32ba5fab7/db-store.js\")\n","extension":".md","dir":"docs","file":"simple-storage","tag":"","section":"Files and Data","i":"4","sectionIndex":"3","createdAt":"2022-07-06T16:05:47Z"},{"name":"Play with Data in Chrome DevTools","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/822","url":"","title":"DevTools","command":"devtools","content":"\n\n\n\n\n \n\n\n## Play with Data in Chrome DevTools\n\n```js\n// Name: Play with Data in Chrome DevTools\n\nimport \"@johnlindquist/kit\"\n\n// Will open a standalone Chrome DevTools window\n// The object passed in will be displayed\n// You can access the object using `x`, e.g., `x.message` will be `Hello world`\ndev({\n message: \"Hello world\",\n})\n```\n\n[Open play-with-data-in-chrome-devtools in Script Kit](https://scriptkit.com/api/new?name=play-with-data-in-chrome-devtools&url=https://gist.githubusercontent.com/johnlindquist/3202a35d448efd09c37c4b49b7f7c95a/raw/187da03b4dae7c1ebe6fb79bd1ea47f7a492cb38/play-with-data-in-chrome-devtools.js\")\n","extension":".md","dir":"docs","file":"devtools","description":"","tag":"","section":"Essentials","i":"6","sectionIndex":"1","createdAt":"2022-07-04T20:06:34Z"},{"name":"Run Commands in the Terminal","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/821","url":"","title":"Built-in Terminal","command":"built-in-terminal","content":"\n\n\n\n\n \n\n\n## Use the Built-in Terminal\n\n```js\n// Name: Run Commands in the Terminal\n\nimport \"@johnlindquist/kit\"\n\nawait term({\n //defaults to home dir\n cwd: `~/.kenv`,\n command: `ls`,\n // The footer is optional. All terms continue with the same shortcuts\n footer: `ctrl+c or cmd+enter to continue`,\n})\n```\n\n[Open run-commands-in-the-terminal in Script Kit](https://scriptkit.com/api/new?name=run-commands-in-the-terminal&url=https://gist.githubusercontent.com/johnlindquist/e1fbd791d67d772c047f2afcab087cff/raw/37f5ab92f8caafbf437d4f234818ebae67fb7fba/run-commands-in-the-terminal.js\")\n\n> The shell defaults to `zsh`. You can change your shell by setting the `KIT_SHELL` environment variable in the ~/kenv/.env, but most of the testing has been done with `zsh`.\n","extension":".md","dir":"docs","file":"built-in-terminal","description":"","tag":"","section":"Essentials","i":"4","sectionIndex":"1","createdAt":"2022-07-04T20:06:33Z"},{"name":"Input Text","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/819","url":"","title":"Text Input","command":"text-input","content":"\n\n\n\n\n \n\n\n## Input Text with `await arg()`\n\nThe simplest form of input you can accept from a user is an `arg()`\n\n```js\n// Name: Input Text\n\nimport \"@johnlindquist/kit\"\n\nlet name = await arg(\"Enter your name\")\n\nawait div(md(`Hello, ${name}`))\n```\n\n[Open input-text in Script Kit](https://scriptkit.com/api/new?name=input-text&url=https://gist.githubusercontent.com/johnlindquist/af8883b05ae34055fff79ec8556e007d/raw/bb5d116f831d3124081867e83710a07e39bf41cd/input-text.js\")\n\n## Select From a List of Strings\n\n```js\n// Name: Select From a List\n\nimport \"@johnlindquist/kit\"\n\nlet fruit = await arg(\"Pick a fruit\", [\n \"Apple\",\n \"Banana\",\n \"Cherry\",\n])\n\nawait div(md(`You selected ${fruit}`))\n```\n\n[Open select-from-a-list in Script Kit](https://scriptkit.com/api/new?name=select-from-a-list&url=https://gist.githubusercontent.com/johnlindquist/a53ebfe6372eb3ad3aade06e2d11ef51/raw/b1939b6cceb669f2bbaeec5e6b3af2549994e214/select-from-a-list.js\")\n\n## Select From a List of Objects\n\n```js\n// Name: Select From a List of Objects\n\nimport \"@johnlindquist/kit\"\n\nlet { size, weight } = await arg(\"Select a Fruit\", [\n {\n name: \"Apple\",\n description: \"A shiny red fruit\",\n // add any properties to \"value\"\n value: {\n size: \"small\",\n weight: 1,\n },\n },\n {\n name: \"Banana\",\n description: \"A long yellow fruit\",\n value: {\n size: \"medium\",\n weight: 2,\n },\n },\n])\n\nawait div(\n md(\n `You selected a fruit with size: ${size} and weight: ${weight}`\n )\n)\n```\n\n[Open select-from-a-list-of-objects in Script Kit](https://scriptkit.com/api/new?name=select-from-a-list-of-objects&url=https://gist.githubusercontent.com/johnlindquist/1643c1f34cc146e19c01b5144c542b6f/raw/ac3a2bc71c27d1ee58cf83394c8755a005d2a567/select-from-a-list-of-objects.js\")\n\n## Display a Preview When Focusing a Choice\n\n```js\n// Name: Display a Preview When Focusing a Choice\n\nimport \"@johnlindquist/kit\"\n\nlet heights = [320, 480, 640]\nlet choices = heights.map(h => {\n return {\n name: `Kitten height: ${h}`,\n preview: () =>\n `\n\n\n\n\n \n\n\n## Display HTML\n\nUse `await div('')` to display HTML.\n\n```js\n// Name: Display HTML\n\nimport \"@johnlindquist/kit\"\n\nawait div(`
Hello World
`)\n```\n\n[Open display-html in Script Kit](https://scriptkit.com/api/new?name=display-html&url=https://gist.githubusercontent.com/johnlindquist/ba1d6754436d898f8cebe8558647e720/raw/468e99941e8c63eff51ba24b6cb7c86bb9dd70fe/display-html.js\")\n\n## Display HTML with CSS\n\nScript Kit bundles [Tailwind CSS](https://tailwindcss.com/).\n\n```js\n// Name: Display HTML with CSS\n\nimport \"@johnlindquist/kit\"\n\nawait div(\n `
Hello World
`\n)\n```\n\n[Open display-html-with-css in Script Kit](https://scriptkit.com/api/new?name=display-html-with-css&url=https://gist.githubusercontent.com/johnlindquist/02b7a43e5dd49f2e1508d8c110d12371/raw/1d80190f0cfce860078cec799fd614bd6f49a474/display-html-with-css.js\")\n\n## Display Markdown\n\nThe `md()` function will convert Markdown to HTML into HTML that you can pass into div. It will also add the default Tailwind styles so you won't have to think about formatting.\n\n```js\n// Name: Display Markdown\n\nimport \"@johnlindquist/kit\"\n\nlet html = md(`# Hello World`)\n\nawait div(html)\n```\n\n[Open display-markdown in Script Kit](https://scriptkit.com/api/new?name=display-markdown&url=https://gist.githubusercontent.com/johnlindquist/84779dbf8e39212c672b16ee72c68ccf/raw/7e985c988fa6aa878e4c0040dac6b87b8cfb173c/display-markdown.js\")\n","extension":".md","dir":"docs","file":"display-html-and-markdown","description":"","tag":"","section":"Essentials","i":"0","sectionIndex":"1","createdAt":"2022-07-02T01:52:44Z"},{"name":"Speak File","watch":"~/speak.txt","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/814","url":"","title":"Watch for File Changes","command":"watch-for-file-changes","content":"\n\n\n\n\n \n\n\n## Watch a Single File\n\nThe `// Watch` metadata enables you to watch for changes to a file on your system.\n\n```js\n// Name: Speak File\n// Watch: ~/speak.txt\n\nimport \"@johnlindquist/kit\"\n\nlet speakPath = home(\"speak.txt\")\n\ntry {\n let content = await readFile(speakPath, \"utf-8\")\n if (content.length < 60) {\n // We don't want `say` to run too long 😅\n say(content)\n }\n} catch (error) {\n log(error)\n}\n```\n\n[Open speak-file in Script Kit](https://scriptkit.com/api/new?name=speak-file&url=https://gist.githubusercontent.com/johnlindquist/ec65920283c6ef66429a2331cdc81539/raw/98584e1ee1cb6b5f4f235a6873bdfcb709dfb953/speak-file.js\")\n\n## Watch a Directory\n\nThe `// Watch` metadata uses [Chokidar](https://www.npmjs.com/package/chokidar) under the hood, so it supports the same glob patterns.\n\n```js\n// Name: Download Log\n// Watch: ~/Downloads/*\n\nimport \"@johnlindquist/kit\"\n\n// These are optional and automatically set by the watcher\nlet filePath = await arg()\nlet event = await arg()\n\nif (event === \"add\") {\n await appendFile(home(\"download.log\"), filePath + \"\\n\")\n}\n```\n\n[Open download-log in Script Kit](https://scriptkit.com/api/new?name=download-log&url=https://gist.githubusercontent.com/johnlindquist/395ced3283e44c8ed2fea885104a1346/raw/e9b03ddfb0ae969d2b82e45c41ac8680ea2e686b/download-log.js\")\n","extension":".md","dir":"docs","file":"watch-for-file-changes","description":"","tag":"","section":"Script Metadata","i":"2","sectionIndex":"2","createdAt":"2022-06-30T02:17:17Z"},{"name":"Express Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/813","url":"","title":"npm Packages","command":"npm-packages","content":"\n\n\n\n\n \n\n\n## Install Express from npm\n\n```js\n// Name: Express Example\n\nimport \"@johnlindquist/kit\"\n\nlet express = await npm(\"express\")\nlet detect = await npm(\"detect-port\")\n\nlet app = express()\n\napp.get(\"/\", (req, res) => {\n res.send(`Hello Script Kit!`)\n})\n\nlet port = await detect()\napp.listen(port)\n\nawait hide() // in case the terminal is open from installing packages\n\nawait browse(`http://localhost:${port}`)\n```\n\n[Open express-example in Script Kit](https://scriptkit.com/api/new?name=express-example&url=https://gist.githubusercontent.com/johnlindquist/52c9ab749f8483a15ccbd28631db2df1/raw/e8bc1e1dec05fd17e36cafbf21ada409b91a6fa9/express-example.js\")\n\n> You can terminate a long-running process like above from the menubar dropdown menu or by pressing `cmd+p` from the Script Kit window to list running processes.\n","extension":".md","dir":"docs","file":"npm-packages","description":"","tag":"","section":"Essentials","i":"7","sectionIndex":"1","createdAt":"2022-06-30T02:17:17Z"},{"name":"Select a Path","note":"Dropping one or more files returns an array of file information","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/809","url":"","title":"Select a Path","command":"select-a-path","content":"\n\n\n\n\n \n\n\n## Select a Path\n\n```js\n// Name: Select a Path\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = await path()\n\nawait div(md(`You selected ${filePath}`))\n```\n\n[Open select-a-path in Script Kit](https://scriptkit.com/api/new?name=select-a-path&url=https://gist.githubusercontent.com/johnlindquist/68ae880d76f6d92b1aa9994501465f2b/raw/839a08ef025a07e5d5e292c8730d7c631b934798/select-a-path.js\")\n\n## Select a Path with Options\n\n```js\n// Name: Select a Path with Options\n\nimport \"@johnlindquist/kit\"\n\nawait path({\n hint: `Select a path containing JS files`,\n onlyDirs: true,\n onChoiceFocus: async (input, { focused }) => {\n let focusedPath = focused.value\n try {\n let files = await readdir(focusedPath)\n let hasJS = files.find(f => f.endsWith(\".js\"))\n\n setPreview(\n md(\n `${\n hasJS ? \"✅ Found\" : \"🔴 Didn't find\"\n } JS files`\n )\n )\n } catch (error) {\n log(error)\n }\n },\n})\n```\n\n[Open select-a-path-with-options in Script Kit](https://scriptkit.com/api/new?name=select-a-path-with-options&url=https://gist.githubusercontent.com/johnlindquist/8ec7f7178cd44481aed4e968fd83da3f/raw/c8a4d8deacaa192f07fbdfed7c17a75558bc99a7/select-a-path-with-options.js\")\n\n## Drag and Drop\n\n```js\n// Name: Drop Example\n\nimport \"@johnlindquist/kit\"\n\n// Note: Dropping one or more files returns an array of file information\n// Dropping text or an image from the browser returns a string\nlet fileInfos = await drop()\n\nlet filePaths = fileInfos.map(f => f.path).join(\",\")\n\nawait div(md(filePaths))\n```\n\n[Open drop-example in Script Kit](https://scriptkit.com/api/new?name=drop-example&url=https://gist.githubusercontent.com/johnlindquist/f7937ef8b3d5827b5aaa17b59dc4e223/raw/183d7cdd3c3e687cdd4fd6fd833abd957e57d3de/drop-example.js\")\n\n## Select from Finder Prompts\n\n```js\n// Name: Select from Finder Prompt\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = await selectFile()\n\nlet folderPath = await selectFolder()\n\nawait div(md(`You selected ${filePath} and ${folderPath}`))\n```\n\n[Open select-from-finder-prompt in Script Kit](https://scriptkit.com/api/new?name=select-from-finder-prompt&url=https://gist.githubusercontent.com/johnlindquist/d27e5970cb6284bd28b746eaeb49df78/raw/5f4b446f2b5a61435a651e0132e878fae9a4f819/select-from-finder-prompt.js\")\n","extension":".md","dir":"docs","file":"select-a-path","description":"","tag":"","section":"Essentials","i":"2","sectionIndex":"1","createdAt":"2022-06-30T02:17:14Z"},{"name":"Stand Up and Stretch","schedule":"*/15 * * * *","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/806","url":"https://gist.githubusercontent.com/johnlindquist/4a857741902927cc97e10db7a43b497d/raw/e01f61d697941fe0f0e90d51d5eb35f81b214be7/stand-up-and-stretch.ts","title":"Run a Script on a Schedule","command":"run-a-script-on-a-schedule","content":"\n\n\n\n\n \n\n\n## Run a Script on a Schedule\n\nUse cron syntax to run scripts on a schedule. The following example will show a notification to stand up and stretch every 15 minutes.\n\n```js\n// Name: Stand Up and Stretch\n// Schedule: */15 * * * *\n\nimport \"@johnlindquist/kit\"\n\nnotify(`Stand up and stretch`)\n```\n\n[Open stand-up-and-stretch in Script Kit](https://scriptkit.com/api/new?name=stand-up-and-stretch&url=https://gist.githubusercontent.com/johnlindquist/4a857741902927cc97e10db7a43b497d/raw/e01f61d697941fe0f0e90d51d5eb35f81b214be7/stand-up-and-stretch.ts\")\n\n[Crontab.guru](https://crontab.guru/) is a great utility to help generate and understand cron syntax.\n","extension":".md","dir":"docs","file":"run-a-script-on-a-schedule","description":"","tag":"","section":"Script Metadata","i":"1","sectionIndex":"2","createdAt":"2022-06-30T02:17:13Z"},{"name":"Read Files","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/805","url":"","title":"Read Files","command":"read-files","content":"\n\n\n\n\n \n\n\n## Read Files\n\n```js\n// Name: Read Files\n\nimport \"@johnlindquist/kit\"\n\nlet pkg = await readFile(\n home(\".kenv\", \"package.json\"),\n \"utf-8\"\n)\n\nawait editor({\n value: pkg,\n language: \"json\",\n})\n```\n\n[Open read-files in Script Kit](https://scriptkit.com/api/new?name=read-files&url=https://gist.githubusercontent.com/johnlindquist/00093125f3735bed8062dce0e20af4f8/raw/faec10fc8f99cc0744e9e840cfadb6cbe88e3a32/read-files.js\")\n","extension":".md","dir":"docs","file":"read-files","description":"","tag":"","section":"Files and Data","i":"0","sectionIndex":"3","createdAt":"2022-06-30T02:17:12Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/803","url":"","title":"Share Scripts","command":"share-scripts","content":"\n\n\n\n\n \n\n\n## Get Featured\n\nFeatured scripts are displayed in:\n\n- The `hot` tab of the Script Kit main window\n- On the [Community Scripts](https://www.scriptkit.com/scripts) page\n\nTo get featured, post your script to the [Script Kit Github discussions Share page](https://github.com/johnlindquist/kit/discussions/categories/share). With a script focused in the Script Kit main window, you can press right or cmd+k to bring up a share menu which will automatically walk you through creating a shareable post for the script.\n\nAs a shortcut, hit cmd+s with a script selected to automatically run the \"Share as Discussion\" process.\n\n## Share as a Gist, Link, URL, or Markdown\n\nThe Script Kit main window also includes many other share options:\n\n- Share as Gist cmd+g: Creates as Gist of the selected script, then copies the URL to the clipboard\n- Share as Link opt+s: Creates a private installable kit://link to the selected script, then copies the URL to the clipboard. These links are very long as they encode the entire script into the URL.\n- Share as URL opt+u: Creates a Gist of the selected script, then copies an installable public URL to the clipboard\n- Share as Markdown cmd+m: Copies the selected script as a Markdown snippet to the clipboard\n","extension":".md","dir":"docs","file":"share-scripts","description":"","tag":"","section":"Essentials","i":"8","sectionIndex":"1","createdAt":"2022-06-30T02:17:11Z"},{"name":"Env Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/802","url":"","title":"Environment Variables","command":"environment-variables","content":"\n\n\n\n\n \n\n\n## Read and Write from ~/.kenv/.env\n\nThe `env` helper will read environment variables from ~/.kenv/.env. If the variable doesn't exist, it will prompt you to create it.\n\n```js\n// Name: Env Example\n\nimport \"@johnlindquist/kit\"\n\nlet KEY = await env(\"MY_KEY\")\n\nawait div(md(`You loaded ${KEY} from ~/.kenv/.env`))\n```\n\n[Open env-example in Script Kit](https://scriptkit.com/api/new?name=env-example&url=https://gist.githubusercontent.com/johnlindquist/84068b5eb52a366b0746aff3f984f3dd/raw/c22f3160535158d2d38952b4a7ee22a105d9359f/env-example.js\")\n\n## Choose an Environment Variable\n\nIf you pass a function as the second argument to `env`, it will only be called if the variable doesn't exist.\nThis allows you to set Enviroment Variables from a list, an API, or any other data source.\n\n```js\n// Name: Choose an Environment Variable\n\nimport \"@johnlindquist/kit\"\n\nlet MY_API_USER = await env(\"MY_API_USER\", async () => {\n return await arg(\"Select a user for your API\", [\n \"John\",\n \"Mindy\",\n \"Joy\",\n ])\n})\n\nawait div(\n md(\n `You selected ${MY_API_USER}. Running this script again will remember your choice`\n )\n)\n```\n\n[Open choose-an-environment-variable in Script Kit](https://scriptkit.com/api/new?name=choose-an-environment-variable&url=https://gist.githubusercontent.com/johnlindquist/cbc1029ea6abcdb8658cc3919b05875c/raw/cc8ca92d9edc57e16e4fcf2978e5560c6c73ab71/choose-an-environment-variable.js\")\n","extension":".md","dir":"docs","file":"environment-variables","description":"","tag":"","section":"Essentials","i":"5","sectionIndex":"1","createdAt":"2022-06-30T02:17:10Z"},{"name":"Download a File","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/800","url":"","title":"Download Files","command":"download-files","content":"\n\n\n\n\n \n\n\n## Download a Single File\n\n```js\n// Name: Download a File\n\nimport \"@johnlindquist/kit\"\n\nlet url = \"https://www.scriptkit.com/assets/logo.png\"\nlet buffer = await download(url)\n\nlet fileName = path.basename(url)\nlet filePath = home(fileName)\n\nawait writeFile(filePath, buffer)\n```\n\n[Open download-a-file in Script Kit](https://scriptkit.com/api/new?name=download-a-file&url=https://gist.githubusercontent.com/johnlindquist/b10dbc2218d7c229fd4ed9865739b46f/raw/b9108056d761cdf6b1e8ec5c7d218d11f4002e56/download-a-file.js\")\n","extension":".md","dir":"docs","file":"download-files","description":"","tag":"","section":"Files and Data","i":"2","sectionIndex":"3","createdAt":"2022-06-30T02:17:09Z"},{"name":"Create a Text File","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/796","url":"","title":"Create Files","command":"create-files","content":"\n\n\n\n\n \n\n\n## Create a Text File\n\n```js\n// Name: Create a Text File\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = await path() // Select a path that doesn't exist\n\nlet exists = await pathExists(filePath)\n\nif (!exists) {\n await writeFile(filePath, \"Hello world\")\n} else {\n await div(md(`${filePath} already exists...`))\n}\n```\n\n[Open create-a-text-file in Script Kit](https://scriptkit.com/api/new?name=create-a-text-file&url=https://gist.githubusercontent.com/johnlindquist/24794c9b9bfce36ff898d34019555012/raw/c222c46aefd322ae50c9f5fc9e70ba0d2ef74d26/create-a-text-file.js\")\n\n## Update a Text File\n\n```js\n// Name: Update a Text File\n\nimport \"@johnlindquist/kit\"\n\nlet filePath = home(`my-notes.md`)\n\n// `ensureReadFile` will create the file with the content\n// if it doesn't exist\nlet content = await ensureReadFile(filePath, \"Hello world\")\n\nawait editor({\n value: content,\n onInput: _.debounce(async input => {\n await writeFile(filePath, input)\n }, 200),\n})\n```\n\n[Open update-a-text-file in Script Kit](https://scriptkit.com/api/new?name=update-a-text-file&url=https://gist.githubusercontent.com/johnlindquist/b9aa415d3870b8760c54ca57ccabd77d/raw/d3a4645c645dfb0d1749f1719aee817f723357fc/update-a-text-file.js\")\n","extension":".md","dir":"docs","file":"create-files","description":"","tag":"","section":"Files and Data","i":"1","sectionIndex":"3","createdAt":"2022-06-30T02:17:06Z"},{"name":"Editor Example","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/794","url":"","title":"Built-in Editor","command":"built-in-editor","content":"\n\n\n\n\n \n\n\n## Built-in Editor\n\n```js\n// Name: Editor Example\n\nimport \"@johnlindquist/kit\"\n\nlet result = await editor({\n footer: `Hit cmd+s to continue...`,\n})\n\nawait div(md(result))\n```\n\n[Open editor-example in Script Kit](https://scriptkit.com/api/new?name=editor-example&url=https://gist.githubusercontent.com/johnlindquist/3be99f494f84cea0b21aa673740c0e2e/raw/64757d3981befaf3ef7b3a0eceadab240cd8c2e2/editor-example.js\")\n\n## Load Text in the Editor\n\n```js\n// Name: Load Text Into the Editor\n\nimport \"@johnlindquist/kit\"\n\nlet { data } = await get(\n `https://raw.githubusercontent.com/johnlindquist/kit/main/README.md`\n)\n\nlet result = await editor({\n value: data,\n // Supports \"css\", \"js\", \"ts\", and \"md\". \"md\" is default. More language support coming in future releases.\n language: \"md\",\n footer: `Hit cmd+s to continue...`,\n})\n\nawait div(md(result))\n```\n\n[Open load-text-into-the-editor in Script Kit](https://scriptkit.com/api/new?name=load-text-into-the-editor&url=https://gist.githubusercontent.com/johnlindquist/69efafa66f1c6aa436b8f8283cc1fbba/raw/7f371e045609d3dbee92999b09eac4839262fc9f/load-text-into-the-editor.js\")\n","extension":".md","dir":"docs","file":"built-in-editor","description":"","tag":"","section":"Essentials","i":"3","sectionIndex":"1","createdAt":"2022-06-30T02:17:05Z"},{"shortcut":"cmd shift j","shortcode":"oi","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/792","url":"","title":"Add a Keyboard Shortcut","command":"add-a-keyboard-shortcut","content":"\n\n\n\n\n \n\n\n## // Shortcut Metadata\n\nUse the `// Shortcut` metadata to add a global keyboard shortcut to any script\n\n```js\n// Shortcut: cmd shift j\n\nimport \"@johnlindquist/kit\"\n\nsay(`You pressed command shift j`)\n```\n\n```js\n// Shortcut: opt i\n\nimport \"@johnlindquist/kit\"\n\nsay(`You pressed option i`)\n```\n\n## // Shortcode Metadata\n\nA shortcode allows you quickly run a script without needing to search for it.\n\nTo trigger a `// Shortcode`, type the string of characters from the main menu, then hit `spacebar`. In this example, you would type `oi` then `spacebar` to run this script:\n\n```js\n// Shortcode: oi\n\nimport \"@johnlindquist/kit\"\n\nsay(`You pressed option i`)\n```\n\n## Quick Submit from Hint\n\nA common pattern from Terminal is to quickly submit a script from a hint. Using a bracket around a single character will submit that character when pressed.\n\n```js\nimport \"@johnlindquist/kit\"\n\nlet value = await arg({\n placeholder: \"Continue?\",\n hint: `Another [y]/[n]`,\n})\n\nif (value === \"y\") {\n say(`You pressed y`)\n} else {\n say(`You pressed n`)\n}\n```\n\n## Quick Submit from Choice\n\nIf you need to provide a little more information to the user, use a choice instead of a hint. This allows you to provide a full value that will be submitted instead of just the single letter.\n\n```js\nimport \"@johnlindquist/kit\"\n\nlet value = await arg(\"Select a food\", [\n {\n name: \"[a]pple\",\n value: \"apple\",\n },\n {\n name: \"[b]anana\",\n value: \"banana\",\n },\n {\n name: \"[c]heese\",\n value: \"cheese\",\n },\n])\n\nawait div(md(value))\n```\n","extension":".md","dir":"docs","file":"add-a-keyboard-shortcut","description":"","tag":"","section":"Script Metadata","i":"0","sectionIndex":"2","createdAt":"2022-06-30T02:17:04Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/785","url":"","title":"Accessibility Permissions for Snippets, Keyboard Actions, and Clipboard History","command":"accessibility-permissions-for-snippets-keyboard-actions-and-clipboard-history","content":"Kit.app requires permission for the following reasons:\r\n* Watch user input to trigger Snippets and Clipboard History\r\n* Send keystrokes to trigger for `setSelectedText`, `getSelectedText`, `keyboard.type` and others\r\n* In the future, recording Macros, mouse actions, and more\r\n\r\n❗️ **You must quit Kit.app and re-open it for changes to take effect.** \r\n\r\n![CleanShot 2022-06-20 at 14 27 00](https://user-images.githubusercontent.com/36073/174673600-59020e49-be04-4786-81f7-5bbe20a9ce6c.png)\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2022-06-20T20:30:58Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/624","url":"","title":"Submit From Live Data","command":"submit-from-live-data","content":"\r\n\r\n\r\nSome scenarios require `setInterval` or other \"live data\" utils. This means you can't use `await` on the arg/div/textarea/etc because `await` prevents the script from continuing on to start the `setInterval`.\r\n\r\n![CleanShot 2021-11-28 at 08 58 04](https://user-images.githubusercontent.com/36073/143775792-34c1fb15-21b9-4690-b8e2-23e1447f65e5.gif)\r\n\r\nUse the Promise `then` on arg/div/textarea/etc to allow the script to continue to run to the `setInterval`. Inside of the `then` callback, you will have to clear the interval for your script to continue/complete:\r\n\r\n```js\r\nlet intervalId = 0\r\ndiv(md(`Click a value`)).then(async value => {\r\n clearInterval(intervalId)\r\n\r\n await div(md(value))\r\n})\r\n\r\nintervalId = setInterval(() => {\r\n let value = Math.random()\r\n\r\n setPanel(\r\n md(`\r\n [${value}](submit:${value})\r\n `)\r\n )\r\n}, 1000)\r\n```\r\n\r\n","extension":".md","dir":"help","file":"submit-from-live-data","description":"Use Then to Allow setInterval","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-28T16:01:58Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/620","url":"","title":"New Kit Environment (Kenv)","command":"new-kit-environment-kenv","content":"\r\n\r\n# New Kit Environment\r\n\r\nThis command will create a new directory in `~/.kenv/kenvs` that contains a `scripts` directory as a place to keep scripts separate from your main Kit environment. This is usually done when you want to share scripts with the community/your team or organize them by type (scripts for GitHub, scripts for Google, etc).\r\n\r\n## Limitations\r\n\r\nScripts inside of `kenvs` do not support metadata that allow scripts to run automatically:\r\n\r\n- Schedule\r\n- Watch\r\n- Background\r\n- System\r\n\r\nIf you need to run one of these automatically, move/duplicate it to your main Kit Environment. You can do this from the main scripts menu by pressing cmd+k to see the script options.","extension":".md","dir":"new","file":"kenv-create","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-26T14:40:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/616","url":"","title":"Strict Mode","command":"strict-mode","content":"\r\n\r\n\r\n# Strict Mode\r\n\r\n`strict` is enabled by default and it forces the user to pick an item from the list, preventing them from entering their own text.\r\n\r\nWhen you disabled `strict`, if you type something that eliminates the entire list, then hit Enter, the string from the input will be passed back.\r\n\r\n> Note: If the list values are Objects and the user inputs a String, you will need to handle either type being returned\r\n\r\n```js\r\n// If the list is completely filtered, hitting enter does nothing.\r\nlet fruit = await arg(`You can only pick one`, [\r\n `Apple`,\r\n `Banana`,\r\n `Orange`,\r\n])\r\n\r\n// If the list is completely filtered, hitting enter sends whatever\r\n// is currently in the input.\r\nlet fruitOrInput = await arg(\r\n {\r\n placeholder: `Pick a fruit or type anything`,\r\n strict: false,\r\n },\r\n [`Apple`, `Banana`, `Orange`]\r\n)\r\n\r\nawait textarea(`${fruit} and ${fruitOrInput}`)\r\n```","extension":".md","dir":"help","file":"strict-mode","description":"Allow user to input text when list is filtered","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-22T23:16:24Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/605","url":"","title":"Download Latest Docs","command":"download-latest-docs","content":"\r\n\r\n\r\n# Download Latest Docs\r\n\r\nHit Enter to grab the latest docs.json from scriptkit.com. Docs will automatically refresh when you re-open the Docs tab.\r\n\r\n## Docs Constantly Updated\r\n\r\nThe docs are generated from GitHub discussions (just like this one). When I update a GitHub discussion in the \"Docs\" category, discussion triggers a webhook that builds a new docs.json file that can be downloaded by the Kit.app.","extension":".md","dir":"help","file":"download-docs","description":"Grab the latest version of the docs.json","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-19T23:27:31Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/604","url":"","title":"Create Scripts from Docs","command":"create-scripts-from-docs","content":"\r\n\r\n# Helpful Snippets in Docs\r\n\r\nThe [Docs](submit:docs) tab contains many helpful snippets to help you start your scripts. You can click on the links above the snippets to create the script just like the one below:\r\n\r\n```js\r\nlet value = await arg(\"Hello world!\")\r\nawait div(`${value} 🌎`, `p-5 text-5xl flex justify-center items-center`)\r\n```","extension":".md","dir":"new","file":"docs","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-19T23:19:12Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/603","url":"","title":"Settings","command":"settings","content":"\r\n\r\n# Settings\r\n\r\nHit Enter to modify Kit.app settings:\r\n- Toggle the menu bar icon\r\n- Toggle open at login\r\n- Toggle auto-update","extension":".md","dir":"kit","file":"settings","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-19T19:38:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/594","url":"","title":"Real-Time Results from Input","command":"real-time-results-from-input","content":"\r\n\r\n\r\nWhen you pass a function as the second argument of `arg`, you can take the current `input` and return a string. Kit.app will then render the results as HTML. The simplest example looks like this:\r\n\r\n```js\r\nawait arg(\"Start typing\", input => input)\r\n```\r\n\r\nIf you want to make it look a bit nicer, you can wrap the output with some HTML:\r\n\r\n```js\r\nawait arg(\r\n \"Type something\",\r\n input =>\r\n `
\r\n${input || `Waiting for input`}\r\n
`\r\n)\r\n```\r\n\r\nGrowing on the example above, here's a Celsius to Fahrenheit converter:\r\n\r\n```js\r\nlet cToF = celsius => {\r\n return (celsius * 9) / 5 + 32\r\n}\r\n\r\nawait arg(\r\n \"Enter degress in celsius\",\r\n input =>\r\n `
\r\n${input ? cToF(input) + \"f\" : `Waiting for input`}\r\n
`\r\n)\r\n```","extension":".md","dir":"help","file":"real-time","description":"Update output on every keypress","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-15T15:54:11Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/589","url":"","title":"Flags for Choices","command":"flags-for-choices","content":"\r\n\r\n\r\n\r\n# Flags for Choices\r\n\r\nTo add an options menu to your choices, you must provide a `flags` object. If one of the keyboard shortcuts are hit, or the user selects the option, then the `flag` global will have the matching key from your flags set to `true`:\r\n\r\n\r\n```js\r\nlet urls = [\r\n \"https://scriptkit.com\",\r\n \"https://egghead.io\",\r\n \"https://johnlindquist.com\",\r\n]\r\n\r\nlet flags = {\r\n open: {\r\n name: \"Open\",\r\n shortcut: \"cmd+o\",\r\n },\r\n copy: {\r\n name: \"Copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n}\r\n\r\nlet url = await arg(\r\n { placeholder: `Press 'right' to see menu`, flags },\r\n urls\r\n)\r\n\r\nif (flag?.open) {\r\n $`open ${url}`\r\n} else if (flag?.copy) {\r\n copy(url)\r\n} else {\r\n console.log(url)\r\n}\r\n```\r\n\r\nUsing the same script above, In the terminal, you would pass an open flag like so:\r\n\r\n```bash\r\nmy-sites --open\r\n```","extension":".md","dir":"help","file":"flags","description":"How to add a secondary menu to each choice","tag":"arg","section":"","i":"","sectionIndex":"","createdAt":"2021-11-13T04:46:39Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/588","url":"","title":"Run Scripts from Other Apps","command":"run-scripts-from-other-apps","content":"\r\n\r\n# Run Scripts from Other Apps\r\n\r\nAre you a fan of?\r\n- [Keyboard Maestro](https://www.keyboardmaestro.com/main/)\r\n- [Better Touch Tool](https://folivora.ai/)\r\n- [Karabiner](https://karabiner-elements.pqrs.org/)\r\n- [Raycast](https://www.raycast.com/)\r\n- [Alfred](https://www.alfredapp.com/)\r\n\r\nWe love all these tools! So we made sure the scripts you create in Script Kit can be invoked by them too:\r\n\r\nIf you have a script named `center-app`, then you can paste the following snippet into the \"scripts\" section of any of these tools.\r\n\r\n```bash\r\n~/.kit/kar center-app\r\n```\r\n\r\n`kar` is an executable that takes the script name and sends it to Kit.app to run.\r\n\r\n> It's named `kar` because we're HUGE fans of [karabiner](https://karabiner-elements.pqrs.org/) and using \"kit kar\" as a transport\r\n> for scripts into the app makes us giggle 😇\r\n\r\nAny arguments you pass to the script will also be sent along. So if you want to run `center-app` with a padding of `50`:\r\n\r\n```bash\r\n~/.kit/kar center-app 50\r\n```","extension":".md","dir":"help","file":"other-apps","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T22:26:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/586","url":"","title":"Selecting Files","command":"selecting-files","content":"\r\n\r\n\r\n# Selecting Files\r\n\r\nYou can get files into your scripts in many different ways.\r\n\r\n## Currently Selected Files\r\n\r\nOne of the most convenient is to just run a script on the currently selected files in Finder:\r\n\r\n```js\r\n// Always returns a string\r\nlet filePath = await getSelectedFile()\r\n// If multiple files selected, split by \"\\n\"\r\nif (filePath.includes(\"\\n\")) {\r\n let filePaths = filePath.split(\"\\n\")\r\n}\r\n```\r\n\r\n## Prompt for Selection\r\n\r\nOtherwise, you can prompt to select a file:\r\n\r\n```js\r\n// Open the Finder prompt for file selection\r\nlet filePath = await selectFile()\r\n```\r\n\r\n```js\r\n// Open the Finder prompt for folder selection\r\nlet folderPath = await selectFolder()\r\n```\r\n\r\n## Drag and Drop\r\n\r\n```js\r\n\r\n// Kit.app opens a UI where you can drop files/text/links\r\n// Dropping files will result in an Array of files object\r\nlet dropResult = await drop()\r\ndevTools(dropResult)\r\n```\r\n","extension":".md","dir":"help","file":"selecting-files","description":"selectFile, selectFolder, drop","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T16:57:09Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/582","url":"","title":"The Read/Edit/Write Loop","command":"the-readeditwrite-loop","content":"\r\n\r\nIf want to quickly jot down notes or just edit text files, use the read, edit, write loop:\r\n\r\n```js\r\nlet filePath = tmpPath(\"notes.md\") //tmpPath is a temp dir based on the script name\r\n\r\nlet contents = await ensureReadFile(filePath)\r\n\r\nwhile (true) {\r\n // `editor` will be paywalled in the future. Use `textarea` if you don't want to support us 😢\r\n contents = await editor(contents) // hit cmd+s to \"submit\" and continue back in the loop. cmd+w to close.\r\n await writeFile(filePath, contents)\r\n}\r\n\r\n```","extension":".md","dir":"help","file":"read-edit-write","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T04:58:24Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/581","url":"","title":"Spawn and Position a Window","command":"spawn-and-position-a-window","content":"\r\n\r\n\r\n# Spawn and Position a Window\r\n\r\nYou can control the size/position of each `show` window you create, but you'll need some info from the current screen (especially with a multi-monitor setup!) to be able to position the window where you want it:\r\n\r\n```js\r\nlet width = 480\r\nlet height = 320\r\n\r\nlet { workArea } = await getActiveScreen()\r\nlet { x, y, width: workAreaWidth } = workArea\r\n\r\nshow(\r\n md(`\r\n# I'm in the top right of the current screen!\r\n\r\n
\r\n😘\r\n
\r\n`),\r\n {\r\n width,\r\n height,\r\n x: x + workAreaWidth - width,\r\n y: y,\r\n }\r\n)\r\n```","extension":".md","dir":"help","file":"spawn-and-position","description":"show, getActiveScreen","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T04:44:55Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/580","url":"","title":"Quickly Clone Project Templates","command":"quickly-clone-project-templates","content":"\r\n\r\n# Quickly Clone Project Templates\r\n\r\nWe're developers. We clone project templates from github. [degit](https://www.npmjs.com/package/degit) is available on the global scope for exactly this scenario.\r\n\r\n```js\r\nlet projectName = await arg(\"Name your project\")\r\nlet targetDir = home(\"projects\", projectName)\r\n\r\nawait degit(`https://github.com/sveltejs/template`).clone(\r\n targetDir\r\n)\r\n\r\nedit(targetDir)\r\n```","extension":".md","dir":"help","file":"degit","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T04:06:48Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/579","url":"","title":"Downloading Files","command":"downloading-files","content":"\r\n\r\n\r\n# Download Files\r\n\r\nThe [download](https://www.npmjs.com/package/download) npm package is exposed globally for you to easily download files:\r\n\r\n```js\r\nlet dest = tmpPath() //tmp path is a path generated based on the script name\r\n\r\nawait download(\r\n `https://johnlindquist.com/images/logo/john@2x.png`,\r\n dest\r\n)\r\n\r\n// open the dir in finder to check out your newly downloaded file\r\nawait $`open ${dest}`\r\n```","extension":".md","dir":"help","file":"downloading-files","description":"download","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-12T02:36:14Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/562","url":"","title":"Quick Keys","command":"quick-keys","content":"\r\n\r\n# Quick Keys\r\n\r\nA quick key allows you to bind a single key to submit a prompt.\r\n\r\nYou can add quick keys inside the \"hint\" if you don't want to bother with choices:\r\n\r\n```js\r\n//Type \"y\" or \"n\"\r\nlet confirm = await arg({\r\n placeholder: \"Eat a taco?\",\r\n hint: `[y]es/[n]o`,\r\n})\r\n\r\nconsole.log(confirm) //\"y\" or \"n\"\r\n```\r\n\r\nOtherwise, add the quick keys in the `name` of the choices and it will return the quick key:\r\n\r\n```js\r\n // Type \"a\", \"b\", or \"g\"\r\nlet fruit = await arg(`Pick one`, [\r\n `An [a]pple`,\r\n `A [b]anana`,\r\n `a [g]rape`,\r\n])\r\n\r\nconsole.log(fruit) //\"a\", \"b\", or \"g\"\r\n```\r\n\r\nYou can add a value, then typing the quick key will return the value:\r\n\r\n```js\r\n// Type \"c\" or \"a\"\r\nlet vegetable = await arg(\"Pick a veggie\", [\r\n { name: \"[C]elery\", value: \"Celery\" },\r\n { name: \"C[a]rrot\", value: \"Carrot\" },\r\n])\r\n\r\nconsole.log(vegetable) //\"Celery\" or \"Carrot\"\r\n```","extension":".md","dir":"help","file":"quick-keys","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-09T03:10:10Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/555","url":"","title":"Creating Previews","command":"creating-previews","content":"\r\n\r\n\r\n# Creating Previews\r\n\r\nEach choice in an `arg` can have an associated `preview`. Previews gracefully enhance from a string all the way up to multiple async functions that return strings based on choice.\r\n\r\nYou can toggle the preview pane open and closed with cmd+P\r\n\r\n\r\n```js\r\n// Just a string\r\nawait arg(\r\n \"Select a fruit\",\r\n md(`I recommend typing \"Apple\"`) // \"md\" converts strings to HTML\r\n)\r\n```\r\n\r\n```js\r\n// A function, takes typed \"input\", returns string\r\nawait arg(\"Select a fruit\", input =>\r\n md(`You typed \"${input}\"`)\r\n)\r\n```\r\n\r\n```js\r\n// An async function, takes typed \"input\", returns string\r\n// `hightlight` requires \"async\" takes markdown, applies code highlighting\r\n\r\nawait arg(\r\n \"Select a fruit\",\r\n async input =>\r\n await highlight(` \r\n~~~js\r\nawait arg(\"${input}\")\r\n~~~\r\n `)\r\n)\r\n```\r\n\r\n```js\r\n// A \"preview\" per choice\r\nawait arg(\"Select a fruit\", [\r\n { name: \"Apple\", preview: `Apple, yum! 🍎` },\r\n { name: \"Banana\", preview: `Banana, yum too! 🍌` },\r\n])\r\n```\r\n\r\n```js\r\n// Async \"preview\" per choice\r\nlet preview = async ({ name, input }) =>\r\n await highlight(`\r\n~~~js\r\n// ${name}\r\nawait arg(\"${input}!\")\r\n~~~\r\n`)\r\n```\r\n\r\n```js\r\n//\"input\" param is required to switch prompt mode from \"filter list\" to \"generate list\"\r\nawait arg(\"Select a fruit\", async input => {\r\n return [\r\n { name: `Apple ${input}`, preview },\r\n { name: `Banana ${input}`, preview },\r\n ]\r\n})\r\n```\r\n\r\n```js\r\n// Static preview with static choices\r\nawait arg(\r\n {\r\n preview: md(`\r\n# Pick a fruit\r\n\r\n\r\n `),\r\n },\r\n [\"Apple\", \"Banana\", \"Orange\"]\r\n)\r\n```\r\n\r\n```js\r\n// Dynamic choices, static preview\r\nawait arg(\r\n {\r\n preview: async () =>\r\n await highlight(`\r\n## This is just information\r\n\r\nUsually to help you make a choice\r\n \r\nJust type some text to see the choices update\r\n`),\r\n },\r\n async input => {\r\n return Array.from({ length: 10 }).map(\r\n (_, i) => `${input} ${i}`\r\n )\r\n }\r\n)\r\n```","extension":".md","dir":"help","file":"preview","description":"arg, preview","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-06T15:55:41Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/548","url":"","title":"Check for Updates","command":"check-for-updates","content":"\n \n# Check for Updates\n\nKit.app will check for updates each time your machine wakes from sleep. But if you heard about an update and just can't wait, trigger this command to grab it.\n","extension":".md","dir":"kit","file":"update","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:37Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/547","url":"","title":"Toggle Tray Icon","command":"toggle-tray-icon","content":"\n \n# Toggle Tray Icon\n\nToggle if the system tray icon is visible\n","extension":".md","dir":"kit","file":"toggle-tray","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:36Z"},{"system":"unlock-screen","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/546","url":"","title":"View System Event Scripts","command":"view-system-event-scripts","content":"\n \n# View System Event Scripts\n\nThis menu shows scripts that run on system events.\n\nAdd the `System` metadata to run your script on a system event\n\n```js\n// System: unlock-screen\n```\n\nAvailable events:\n\n- suspend\n- resume\n- on-ac\n- on-battery\n- shutdown\n- lock-screen\n- unlock-screen\n- user-did-become-active\n- user-did-resign-active\n- Read about the available events [here](https://www.electronjs.org/docs/latest/api/power-monitor#events)\n\n> Note: YMMV based on your specific machine setup.\n","extension":".md","dir":"kit","file":"system-events","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/545","url":"","title":"Sync $PATH from Terminal to Kit.app","command":"sync-dollarpath-from-terminal-to-kitapp","content":"\n \n# Sync $PATH from Terminal to Kit.app\n\nHave a command that's working in your terminal, but doesn't work when you call it with Script Kit?\n\nUse this to sync up your \"PATH\" from your terminal to the \"PATH\" that Script Kit will use.\n\nYou can manually edit a `PATH` value any time in `~/.kenv/.env`\n","extension":".md","dir":"kit","file":"sync-path-instructions","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:35Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/544","url":"","title":"Switch to TypeScript Mode","command":"switch-to-typescript-mode","content":"\n \n# Switch to TypeScript Mode\n\nPrefer TypeScript for your scripts? Hit _Enter_!\n","extension":".md","dir":"kit","file":"switch-to-ts","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:34Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/543","url":"","title":"Switch to JavaScript Mode","command":"switch-to-javascript-mode","content":"\n \n# Switch to JavaScript Mode\n\nPrefer JavaScript for your scripts? Hit _Enter_!\n","extension":".md","dir":"kit","file":"switch-to-js","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/542","url":"","title":"Prepare Script for Stream Deck","command":"prepare-script-for-stream-deck","content":"\n \n# Prepare Script for Stream Deck\n\nWant to map a script to your Stream Deck buttons? Use this to walk you through the process!\n","extension":".md","dir":"kit","file":"stream-deck","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:33Z"},{"schedule":"*/10 * * * * *","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/541","url":"","title":"View Schedule","command":"view-schedule","content":"\n \n# View Schedule\n\nList all of the scheduled scripts and see the next time time they'll run.\n\nSchedule a script to run on a cron with the `Schedule` metadata:\n\n```js\n// Schedule: */10 * * * * *\n```\n\nUse [https://crontab.guru/](https://crontab.guru/) to easily generate cron syntax.\n","extension":".md","dir":"kit","file":"schedule","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:32Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/540","url":"","title":"Quit","command":"quit","content":"\n \n# Quit\n\nSee you soon! 👋\n","extension":".md","dir":"kit","file":"quit","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:31Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/539","url":"","title":"Open Script Kit at Login","command":"open-script-kit-at-login","content":"\n \n# Open Script Kit at Login\n\nToggle if Script Kit opens when you log on\n","extension":".md","dir":"kit","file":"open-at-login","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:30Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/538","url":"","title":"Manage npm Packages","command":"manage-npm-packages","content":"\n \n# Manage npm Packages\n\nThis will help you install/uninstall packages from your `~/.kenv/node_modules`\n\n> Note: You can use the `npm` method in your script to prompt the user to auto-install:\n\n```js\nlet express = await npm(\"express\")\n```\n","extension":".md","dir":"kit","file":"manage-npm","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:30Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/537","url":"","title":"Open kit.log","command":"open-kitlog","content":"\n \n# Open kit.log\n\nView the Kit.app log at `~/.kit/logs/kit.log`\n","extension":".md","dir":"kit","file":"kit-log","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:29Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/536","url":"","title":"Clear Kit Prompt Cache","command":"clear-kit-prompt-cache","content":"\n \n# Clear Kit Prompt Cache\n\nEach time you move or resize the prompt around for your scripts, Script Kit will store the position and size. If you want to reset the position of you prompts back to the centered defaults, then run this command.\n","extension":".md","dir":"kit","file":"kit-clear-prompt","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:28Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/535","url":"","title":"Manage Kit Environments","command":"manage-kit-environments","content":"\n \n# Manage Kit Environments\n\nA \"kenv\" (Kit Environment) is a directory with a `scripts` directory. This is the place to create a kenv to manage scripts for your dev team or projects that expose APIs (GitHub, Vercel, etc). We'll be releasing official Script Kit kenvs in the future to show off some of the best practices. 👍\n\nClone, create new, link, push, pull, remove. This menu allows you to manage any of the kenvs you've added to your machine.\n","extension":".md","dir":"kit","file":"kenv-manage","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/534","url":"","title":"Credits","command":"credits","content":"\n \n# Credits\n\n## John Lindquist\n\nDevelopment\n\n- [https://johnlindquist.com](https://johnlindquist.com)\n- [@johnlindquist](https://twitter.com/johnlindquist)\n\n## Vojta Holik\n\nDesign\n\n- [https://vojta.io/](https://vojta.io/)\n- [@vjthlk](https://twitter.com/vjthlk)\n\n## Supported By\n\n- [egghead.io](https://egghead.io)\n","extension":".md","dir":"kit","file":"credits","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:27Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/533","url":"","title":"Generate bin Files","command":"generate-bin-files","content":"\n \n# Generate bin Files\n\nIf you manually manage files in the `scripts` dir (instead of using Kit.app or the `kit` CLI) you may run into the scenarios where you have to re-generate all the `bin` executables. This will do that for you.\n","extension":".md","dir":"kit","file":"create-all-bins","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:26Z"},{"shortcut":"cmd option g","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/532","url":"","title":"Add/Change a Script Shortcut","command":"addchange-a-script-shortcut","content":"\n \n# Add/Change a Script Shortcut\n\nThis list all the scripts and allows you to add a shortcut to it.\n\nYou can manually add shortcuts to scripts like so:\n\n```js\n// Shortcut: cmd option g\n```\n\nThis menu will manage that for you\n","extension":".md","dir":"kit","file":"change-shortcut","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:25Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/531","url":"","title":"Change Script Kit Shortcut","command":"change-script-kit-shortcut","content":"\n \n# Change Script Kit Shortcut\n\nDon't like `cmd+;`? Change it here!\n","extension":".md","dir":"kit","file":"change-main-shortcut","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:25Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/530","url":"","title":"Change Editor","command":"change-editor","content":"\n \n# Change Editor\n\nThis will re-prompt you to pick an editor from your PATH by updating your kenv `.env`.\n\nYou can always manually change the editor that Script Kit uses to open files in `~/.kenv/.env`.\n\nThe following would use `code` (assuming is on the \"PATH\").\n\n```bash\nKIT_EDITOR=code\n```\n\nIf `code` isn't on your PATH, you can add the full path to the editor.\n","extension":".md","dir":"kit","file":"change-editor","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:24Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/529","url":"","title":"Add ~/.kit/bin to $PATH","command":"add-kitbin-to-dollarpath","content":"\r\n\r\n\r\n \r\n# Add ~/.kit/bin to $PATH\r\n\r\n> This is similar to VS Code's \"Add `code` to path\"\r\n\r\nYou can run the `kit` CLI from your terminal with\r\n\r\n```bash\r\n~/.kit/bin/kit\r\n```\r\n\r\nbut this option will allow you run the CLI with:\r\n\r\n```bash\r\nkit\r\n```\r\n\r\n> If you're familiar with adding to your `.zshrc`, just add `~/.kit/bin` to your PATH.\r\n\r\nThe `kit` CLI will allow you to run, edit, etc scripts from your terminal.","extension":".md","dir":"kit","file":"add-kit-to-profile","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:23Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/528","url":"","title":"Add ~/.kenv/bin to $PATH","command":"add-kenvbin-to-dollarpath","content":"\n \n# Add ~/.kenv/bin to $PATH\n\nEach time you create a script, Script Kit also generates a command based on the name you can run from the terminal.\n\nIf you create a script named `list-downloads`, then Script Kit creates a `~/.kenv/bin/list-downloads` executable.\n\nThen you can run the command like so in the terminal:\n\n```bash\n~/.kenv/bin/list-downloads\n```\n\nThis will walk you through running the command without the full path:\n\n```bash\nlist-downloads\n```\n","extension":".md","dir":"kit","file":"add-kenv-to-profile","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:23Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/527","url":"","title":"User Input","command":"user-input","content":"\r\n\r\n \r\n# User Input\r\n\r\nReceive text from a user by adding `arg` to your script:\r\n\r\n```js\r\nlet value = await arg()\r\n```\r\n\r\n`arg` will prompt the user to enter text, wait for the text, then return the value of the text to `value` and continue on with the script.\r\nYou can then use `value` as a string in your script however you want.\r\n\r\nIf you want to tell the user what information the prompt expects, provide a string:\r\n\r\n```js\r\nawait arg(\"Please enter your name\")\r\n```\r\n\r\nIf you want give the user options to select, provide an array as the second argument:\r\n\r\n```js\r\nawait arg(\"Select a fruit:\", [\"apple\", \"banana\", \"grape\"])\r\n```\r\n\r\n## Drag and drop\r\n\r\nPrompt the user to drag and drop a file by using the `drop` method:\r\n\r\n```js\r\nlet filePath = await drop()\r\n```\r\n\r\n## Longer Text\r\n\r\nAllow the user to input multiple lines of text using `textarea`:\r\n\r\n```js\r\nlet text = await textarea()\r\n```\r\n\r\nPre-load the `textarea` with text by passing a string:\r\n\r\n```js\r\nlet pleaseEditMe = `Some text I want to edit`\r\nlet text = await textarea(pleaseEditMe)\r\n```\r\n\r\n## Code Editor\r\n\r\n(💵 In the future, using `editor` will require a paid update)\r\n\r\nLaunch a full code editor using `editor`. This is great\r\n\r\n```js\r\nlet text = await editor()\r\n```\r\n\r\nPre-load the `editor` with text and specify a language for code highlighting/features:\r\n\r\n```js\r\nlet text = `\r\n# My Markdown\r\n\r\n* one\r\n* two\r\n`\r\nlet editedText = await editor(text, \"markdown\")\r\nawait div(md(editedText))\r\n```\r\n\r\n## Keyboard Shortcut\r\n\r\n```js\r\nlet keyData = await hotkey()\r\n```\r\n\r\nIf you were to type `cmd+j`, `keyData` would give you the following response:\r\n\r\n```json\r\n{\r\n \"key\": \"j\",\r\n \"command\": true,\r\n \"shift\": false,\r\n \"option\": false,\r\n \"control\": false,\r\n \"fn\": false,\r\n \"hyper\": false,\r\n \"os\": false,\r\n \"super\": false,\r\n \"win\": false,\r\n \"shortcut\": \"command j\"\r\n}\r\n```\r\n","extension":".md","dir":"help","file":"user-input","description":"arg, drop, hotkey, editor, textarea","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:22Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/526","url":"","title":"Run Shell Commands","command":"run-shell-commands","content":"\n \n# Run Shell Commands\n\nScript Kit bundles [zx](https://github.com/google/zx) as the global `$`\n\nExample from their docs (make sure to `cd` to the proper dir)\n\n```js\nawait $`cat package.json | grep name`\n\nlet branch = await $`git branch --show-current`\nawait $`dep deploy --branch=${branch}`\n\nawait Promise.all([\n $`sleep 1; echo 1`,\n $`sleep 2; echo 2`,\n $`sleep 3; echo 3`,\n])\n\nlet name = \"foo bar\"\nawait $`mkdir /tmp/${name}`\n```\n","extension":".md","dir":"help","file":"terminal-app","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:21Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/525","url":"","title":"Store Data","command":"store-data","content":"\n\n# Store Data\n\n## Read/Write to a `.env` file\n\nUse `await env(\"SOME_KEY\")` to check if the value exists in the `~/.kenv/.env` file, if not, prompt the user to enter it and store it for the next time the script is run\n\n```js\nlet value = await env(\"MY_KEY\")\n```\n\n## Read/Write Data to a json file `db`\n\nThe `db` method will create a json file to store values for you in `~/.kenv/db`.\n\n```js\nlet data = await db({ values: [] })\n\nlet value = await arg(\"Type something\")\n\ndata.values.push(value)\nawait data.write()\n```\n","extension":".md","dir":"help","file":"store-data","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:20Z"},{"shortcut":"option+g","shortcode":"g","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/524","url":"","title":"Keyboard Shortcuts","command":"keyboard-shortcuts","content":"\n \n# Keyboard Shortcuts\n\n## Global Shortcuts\n\nAdd a global shortcut to any script by adding the `//Shortcut: ` metadata:\n\n```js\n// Shortcut: option+g\n```\n\nIn the `Run` tab, use the script options menu `>` to change a shortcut. (Hit `cmd+k` to toggle to the options menu)\nIn the `Kit` tab, you can run `Change script shortcut` to list all script with shortcuts and change them from there.\n\n## Shortcodes\n\nIf you have a script you run often, you can also use \"shortcodes\" in the app. Add the following to the top of your script:\n\n```js\n// Shortcode: g\n```\n\nNow, when you launch the app, type `g` then hit `space` to run the script.\n\nThe main menu (`Run New Kit Help Hot`) also uses shortcodes, so typing `h` then space will switch to the `Help` tab. Or `n` then space switches to the `New` tab.\n","extension":".md","dir":"help","file":"shortcuts","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:20Z"},{"schedule":"*/10 * * * * *","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/523","url":"","title":"Schedule a Script","command":"schedule-a-script","content":"\n \n# Schedule a Script\n\nUse cron syntax to run scripts on a schedule:\n\n```js\n// Schedule: */10 * * * * *\n```\n\n> Note: these scripts must not include `arg` or they will time out after 10 seconds\n","extension":".md","dir":"help","file":"schedule","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/522","url":"","title":"Subscribe to the Newsletter","command":"subscribe-to-the-newsletter","content":"\n \n# Subscribe to the Newsletter\n\n- Featured Scripts\n- Latest Updates\n- Tutorials and lessons\n- Script Kit Tips and Tricks\n- Curated dev news\n","extension":".md","dir":"help","file":"join","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/521","url":"","title":"Get Help on Github Discussions","command":"get-help-on-github-discussions","content":"\n \n# Get Help on Github Discussions\n\nThe Script Kit community lives on GitHub discussions.\n\nThis is the place to:\n\n- 🥰 [Share scripts](https://github.com/johnlindquist/kit/discussions/categories/share)\n- 🙏 [Ask questions](https://github.com/johnlindquist/kit/discussions/categories/q-a)\n- 💡 [Discuss ideas](https://github.com/johnlindquist/kit/discussions/categories/ideas)\n- 😱 [Report errors](https://github.com/johnlindquist/kit/discussions/categories/error)\n\nOr just hit _Enter_ to browse all.\n\n> We'll do our best to respond ASAP!\n","extension":".md","dir":"help","file":"get-help","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:18Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/520","url":"","title":"Reading/Writing Files","command":"readingwriting-files","content":"\n \n# Reading/Writing Files\n\nMost of the methods from [fs/promises](https://nodejs.org/api/fs.html#promises-api) and [fs-extra](https://www.npmjs.com/package/fs-extra) are globally available\n\n## Create a File\n\n```js\n// \"home\" is a method that wraps `path.resolve` based on your home directory\nlet filePath = home(\"projects\", \"kit\", \"note.txt\")\n// writes a file to the filePath using `fs-extra's` \"outputFile\"\nawait outputFile(filePath, `Drink more water`)\n```\n\n## Select and Edit a File\n\n```js\n// \"selectFile\" uses Finder's file selector\nlet filePath = await selectFile()\nlet contents = await readFile(filePath, \"utf-8\")\n\n// Pass the text contents into the editor to quickly edit a file\nlet result = await editor(contents)\nawait writeFile(filePath, result)\n```\n","extension":".md","dir":"help","file":"files","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:17Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/519","url":"","title":"FAQ","command":"faq","content":"\n \n# FAQ\n\n## What is Script Kit?\n\nScript Kit is an open-source dev tool for creating, running, editing, and sharing scripts.\n\nThese scripts can run in the Kit.app, the Terminal, GitHub actions, package.json scripts, webhooks, or pretty much anywhere.\n\n## Community of Scripters?\n\nThe main goal of Script Kit is to build a community of people who love to script away the frictions of their day! 🥰\n\n## What are Kit.app, kit, and kenvs?\n\n- Kit.app - The Kit.app provides a UI for your scripts. The app is \"script-driven\" meaning that every time you launch the app, you're really launching a script. The main menu, even though complex, is a script you could write.\n\n- kit - \"kit\" is the sdk of Script Kit\n\n - A bundle of JavaScript common libs wrapped by an API to make writing scripts easier (`get`, `download`, `replace`, `outputFile`, etc)\n - APIs for interacting with your OS (`edit`, `focusTab`, `say`, `notify`, etc)\n - APIs for interacting with Kit.app and Terminal (`arg`, `env`, etc)\n - Scripts and utils for app setup, managing kenvs, parsing scripts, etc\n\n- kenvs - Kit Enviroments (AKA \"kenv\") are directories that contain a \"scripts\" directory. If you point \"kit\" at a \"kenv\", kit will parse the scripts and give you tools to simplify running and managing them.\n","extension":".md","dir":"help","file":"faq","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:17Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/518","url":"","title":"Display Data","command":"display-data","content":"\r\n\r\n\r\n# Display Data\r\n \r\n## Display HTML\r\n\r\nUse the `div` method to display html.\r\n\r\n```js\r\nawait div(`
Hi
`)\r\n```\r\n\r\n### Add Padding\r\n\r\nThe second argument of `div` allows you to add [tailwind](https://tailwindcss.com/) classes to the container of your html. For example, `p-5` will add a `padding: 1.25rem;` to the container.\r\n\r\n```js\r\nawait div(`
Hi
`, `p-5`)\r\n```\r\n\r\n## Display Markdown\r\n\r\nPass a string of markdown to the `md` method. This will convert the markdown to html which you can then pass to the `div`\r\n\r\n```js\r\nlet html = md(`\r\n# Hi\r\n`)\r\nawait div(html)\r\n```\r\n\r\nIf you want to highlight your markdown, pass the markdown string to the `await highlight()` method:\r\n\r\n```js\r\nlet html = await highlight(`\r\n# Hi\r\n`)\r\nawait div(html)\r\n```\r\n\r\n## Create a New Window\r\n\r\nUse the `show` method to spawn a new, persisting window that is disconnected from the script.\r\n\r\nMuch future work will be put into components/widgets that can run outside of the main app UI, but for now, it simply display any html you send to it:\r\n\r\n```js\r\nshow(`\r\n
\r\n Hello there!\r\n
\r\n\r\n`)\r\n```\r\n","extension":".md","dir":"help","file":"display-data","description":"div, show","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:16Z"},{"author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/517","url":"","title":"Take Credit for Your Work","command":"take-credit-for-your-work","content":"\n \n# Take Credit for Your Work\n\nAdd the `Author` and `Twitter` metadata to get credited in the UI and on [scriptkit.com](https://scriptkit.com) for your script:\n\n```js\n// Author: John Lindquist\n// Twitter: @johnlindquist\n```\n","extension":".md","dir":"help","file":"credit","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:15Z"},{"author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/515","url":"","title":"Creating Scripts","command":"creating-scripts","content":"\r\n \r\n# Creating Scripts\r\n\r\n## Pro-tips\r\n\r\n1. The fastest way to create a script: Launch the app as per usual, then type the name of a script that doesn't exist:\r\n\r\n```bash\r\nMy New Script\r\n```\r\n\r\nThen hit _Enter_.\r\n\r\n2. You can use TypeScript by opening the `Kit` tab and \"Switch to TypeScript Mode\"\r\n\r\n3. The files in the `~/.kenv/templates` dir will be used when creating new scripts. You can create a template with your personal data filled out `~/.kenv/templates/john.js` then in your `.env`, set `KIT_TEMPLATE=john`\r\n\r\n```js\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n```\r\n\r\n## Why the Import?\r\n\r\nThe line is _not_ required, but this comment helps code editors to apply the correct type definition files for autocomplete/code-hinting.\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n```","extension":".md","dir":"new","file":"new","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:14Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/514","url":"","title":"Create a Script from a Gist","command":"create-a-script-from-a-gist","content":"\n \n# Create a Script from a Gist\n\nIn the `Run` tab, with a script selected, hit `cmd+g` (think \"g\" for \"gist\").\n\nThis will create a gist then copy gist's url to the clipboard.\n\nYou can share this url with a friend, then they can copy/paste it into this `New from url` command to quickly grab it.\n","extension":".md","dir":"new","file":"new-from-url","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:13Z"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/513","url":"","title":"Get Featured","command":"get-featured","content":"\n \n# Get Featured\n\nSimply share your script to Script Kit's [GitHub Discussions](https://github.com/johnlindquist/kit/discussions/categories/share) in the \"share\" category and it will automatically (after a review period) get added to [scriptkit.com](https://scriptkit.com)\n\nYou'll even have a user page based on your GitHub user name: [scriptkit.com/johnlindquist](https://scriptkit.com/johnlindquist)\n\n## Prep a Script for Discussion\n\nWith a script selected, hit `cmd+s` (think \"s\" for \"share\"). This automatically:\n\n- Creates a gist of your script\n- Creates the install link\n- Copies the script and link to your clipboard\n- Opens the discussions page\n\nSo all you have to do is \"paste\"!\n","extension":".md","dir":"new","file":"browse-examples","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2021-11-05T17:03:13Z"}]
diff --git a/apps/scriptkit/public/data/hot.json b/apps/scriptkit/public/data/hot.json
index f0bbd2a..d8da044 100644
--- a/apps/scriptkit/public/data/hot.json
+++ b/apps/scriptkit/public/data/hot.json
@@ -1 +1 @@
-[{"avatar":"https://avatars.githubusercontent.com/u/91077547?u=912b5cbbf037a7082ac71e829d74641975d545ff&v=4","user":"MartinLednar","author":"Martin Lednár","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1327","url":"https://github.com/johnlindquist/kit/discussions/1327","title":"Jira monthly time logger","name":"Jira monthly time logger","extension":".md","description":"Created by MartinLednar","resourcePath":"/johnlindquist/kit/discussions/1327","createdAt":"2023-08-09T13:12:04Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AU_n2","body":"# About\r\n\r\nLog tasks you worked on and the total hours you worked for current month into jira.\r\n\r\n- [Gist link](https://gist.githubusercontent.com/MartinLednar/3e3b0e8b23c92734bf946662a4f4b502/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n- [Download link](https://scriptkit.com/api/new?name=jira-monthly-time-logger&url=https://gist.githubusercontent.com/MartinLednar/d702e7993415b04f9e85fd29b910e0cd/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n\r\n\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1327","img":"https://avatars.githubusercontent.com/u/91077547?u=912b5cbbf037a7082ac71e829d74641975d545ff&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5396211?u=afab008a61b79b9bf8343040f532cf9cb3e974ee&v=4","user":"nikolovlazar","author":"Lazar Nikolov","twitter":"NikolovLazar","discussion":"https://github.com/johnlindquist/kit/discussions/1326","url":"https://github.com/johnlindquist/kit/discussions/1326","title":"Attach to tmux session with Kitty terminal","name":"Attach to tmux session with Kitty terminal","extension":".md","description":"Created by nikolovlazar","resourcePath":"/johnlindquist/kit/discussions/1326","createdAt":"2023-08-08T14:35:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AU-up","body":"### Prerequisites:\r\n1. Install [Kitty](https://sw.kovidgoyal.net/kitty/)\r\n2. Add `kitty` to `PATH`: `sudo ln -s /Applications/kitty.app/Contents/MacOS/kitty /usr/local/bin/kitty` (assuming `/usr/local/bin` is in your `PATH`)\r\n3. Kit: `Sync $PATH from Terminal to Kit.app`\r\n\r\n\r\n\r\n\r\n\r\n[Open tmux-sesh in Script Kit](https://scriptkit.com/api/new?name=tmux-sesh&url=https://gist.githubusercontent.com/nikolovlazar/21a78f492e117a5e0dca1685cb668f4d/raw/94e44f60ef8807250f365b996b610d8bb05fd311/tmux-sesh.js\")\r\n\r\n```js\r\n// Name: tmux sesh\r\n// Description: Attach to a tmux session\r\n// Author: Lazar Nikolov\r\n// Twitter: @NikolovLazar\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst sessionsCmd = await $`tmux list-sessions`;\r\n\r\nlet sessions = sessionsCmd.stdout\r\n .split('\\n')\r\n .map((line) => line.split(':')[0])\r\n .filter((sesh) => !!sesh);\r\n\r\nlet choice = await arg('Attach to session:', sessions);\r\n\r\nawait $`kitty --hold sh -c \"tmux a -t ${choice}\"`;\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1326","img":"https://avatars.githubusercontent.com/u/5396211?u=afab008a61b79b9bf8343040f532cf9cb3e974ee&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1322","url":"https://github.com/johnlindquist/kit/discussions/1322","title":"Reveal password","name":"Reveal password","extension":".md","description":"Created by abernier","resourcePath":"/johnlindquist/kit/discussions/1322","createdAt":"2023-07-31T06:08:58Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AU0qy","body":"`| ******* |` cmd * → `| toto123 |`\r\n\r\n```ts\r\n// Name: Reveal password\r\n// Shortcut: cmd *\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet js = `\r\ndocument.activeElement.type = document.activeElement.type === 'password' ? 'text' : 'password';\r\n`;\r\n\r\nlet value = await applescript(`\r\ntell application \"Google Chrome\" to tell window 1\r\n\tget execute active tab javascript \"\r\n\r\n${js}\r\n\r\n\"\r\nend tell\r\n`);\r\n```\r\n-- https://gist.github.com/abernier/582e1458195ec34268305298e4b3b86b","value":"https://github.com/johnlindquist/kit/discussions/1322","img":"https://avatars.githubusercontent.com/u/76580?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","author":"Kent C. Dodds","twitter":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1310","url":"https://github.com/johnlindquist/kit/discussions/1310","title":"Gather Town Guest Management","name":"Gather Town Guest Management","extension":".md","description":"Created by kentcdodds","resourcePath":"/johnlindquist/kit/discussions/1310","createdAt":"2023-07-13T19:05:44Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUmWp","body":"I use this script to manage who has access to join my [Gather.town](https://gather.town) space, so I don't have to manually approve folks joining, they don't need me to be there to join, and they can only access it if they're logged in and are on the list (they've purchased a ticket).\r\n\r\nVery cool thing I can throw together to solve my problems in an hour.\r\n\r\nFind the most up-to-date version in my repo: https://github.com/kentcdodds/.kenv/blob/main/scripts/gather-guest.ts\r\n\r\n\r\n[Open gather-guest in Script Kit](https://scriptkit.com/api/new?name=gather-guest&url=https://gist.githubusercontent.com/kentcdodds/592bd3aebb51971c3a968954ede061f6/raw/5bc5d065782e50f269c5ed9090976722fae50140/gather-guest.ts\")\r\n\r\n```js\r\n// Menu: Gather Guest List\r\n// Description: Handle the Guest List for Gather\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport {z} from 'zod'\r\n\r\nconst GuestObjectSchema = z.object({\r\n name: z.string().optional(),\r\n affiliation: z.string().optional(),\r\n role: z.string().optional(),\r\n})\r\nconst GuestsSchema = z.record(z.string().email(), GuestObjectSchema)\r\n\r\nconst GATHER_API_KEY = await env('GATHER_API_KEY', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_API_KEY',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Get a Gather API Key\r\n\r\n[app.gather.town/apikeys](https://app.gather.town/apikeys)\r\n `),\r\n )\r\n})\r\n\r\nconst GATHER_SPACE_ID = await env('GATHER_SPACE_ID', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_SPACE_ID',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Specify the Gather Space ID\r\n\r\nIt's everything after \"app/\" in this URL with \"/\" replaced by \"\\\\\":\r\n\r\nhttps://app.gather.town/app/BL0B93FK23T/example\r\n `),\r\n )\r\n})\r\n\r\nasync function go() {\r\n const params = new URLSearchParams({\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n })\r\n const rawGuests = await fetch(\r\n `https://gather.town/api/getEmailGuestlist?${params}`,\r\n ).then(r => r.json())\r\n\r\n const guests = GuestsSchema.parse(rawGuests)\r\n const choices = [\r\n {name: '➕ Add a guest', value: {type: 'add-guest'}},\r\n ...Object.entries(guests).map(([email, {name, affiliation, role}]) => ({\r\n name: `${email} (${name?.trim() || 'Unnamed'}, ${\r\n affiliation?.trim() || 'Unaffiliated'\r\n }, ${role?.trim() || 'No role'})`,\r\n value: {type: 'modify-guest', email},\r\n })),\r\n ]\r\n const rawSelection = await arg(\r\n {placeholder: 'Which guest would you like to modify?'},\r\n choices,\r\n )\r\n const SelectionSchema = z.union([\r\n z.object({\r\n type: z.literal('add-guest'),\r\n }),\r\n z.object({\r\n type: z.literal('modify-guest'),\r\n email: z.string(),\r\n }),\r\n ])\r\n const selection = SelectionSchema.parse(rawSelection)\r\n switch (selection.type) {\r\n case 'add-guest': {\r\n await addGuest()\r\n return go()\r\n }\r\n case 'modify-guest': {\r\n await modifyGuest(selection.email, guests)\r\n return go()\r\n }\r\n }\r\n}\r\n\r\nasync function addGuest() {\r\n const email = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: `What's the guests' email?`}))\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: {[email]: {}},\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\nasync function modifyGuest(\r\n email: string,\r\n guests: z.infer,\r\n) {\r\n const guest = guests[email]\r\n const action = await arg({placeholder: `What would you like to do?`}, [\r\n {name: 'Remove Guest', value: 'remove'},\r\n {name: `Change Guest Email (${email})`, value: 'change-email'},\r\n {\r\n name: `Change Guest Name (${guest.name?.trim() || 'Unnamed'})`,\r\n value: 'change-name',\r\n },\r\n {\r\n name: `Change Guest Affiliation (${\r\n guest.affiliation?.trim() || 'Unaffiliated'\r\n })`,\r\n value: 'change-affiliation',\r\n },\r\n {\r\n name: `Change Guest Role (${guest.role?.trim() || 'No role'})`,\r\n value: 'change-role',\r\n },\r\n {\r\n name: `Cancel`,\r\n value: 'cancel',\r\n },\r\n ])\r\n switch (action) {\r\n case 'remove': {\r\n delete guests[email]\r\n break\r\n }\r\n case 'change-email': {\r\n const newEmail = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: 'New Email'}))\r\n guests[newEmail] = guests[email]\r\n delete guests[email]\r\n email = newEmail\r\n break\r\n }\r\n case 'change-name': {\r\n const newName = await arg({placeholder: 'New Name'})\r\n if (newName) {\r\n guests[email].name = newName\r\n } else {\r\n delete guests[email].name\r\n }\r\n break\r\n }\r\n case 'change-affiliation': {\r\n const newAffiliation = await arg({\r\n placeholder: 'New Affiliation',\r\n })\r\n if (newAffiliation) {\r\n guests[email].affiliation = newAffiliation\r\n } else {\r\n delete guests[email].affiliation\r\n }\r\n break\r\n }\r\n case 'change-role': {\r\n const newRole = await arg({placeholder: 'New Role'})\r\n if (newRole) {\r\n guests[email].role = newRole\r\n } else {\r\n delete guests[email].role\r\n }\r\n break\r\n }\r\n case 'cancel': {\r\n return go()\r\n }\r\n }\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: guests,\r\n overwrite: true,\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\ngo()\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1310","img":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","author":"Ivan Rybnikov","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1309","url":"https://github.com/johnlindquist/kit/discussions/1309","title":"Correct selection with ChatGPT","name":"Correct selection with ChatGPT","extension":".md","description":"Created by ivryb","resourcePath":"/johnlindquist/kit/discussions/1309","createdAt":"2023-07-13T12:30:37Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUmEr","body":"[Open Correct selection with ChatGPT in Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/646da11d9a5dbb2151a2053c4d510dd0/raw/8f9b16dd5c636ef6192c12b037ad80f7d84d0193/correct-selection-script.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1309","img":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1308","url":"https://github.com/johnlindquist/kit/discussions/1308","title":"Resize and composite images for tiktok","name":"Resize and composite images for tiktok","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1308","createdAt":"2023-07-12T22:37:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUllQ","body":"\r\n[Open tiktok-images in Script Kit](https://scriptkit.com/api/new?name=tiktok-images&url=https://gist.githubusercontent.com/trevor-atlas/9bc38697613660a228d89f45c5d5ead9/raw/cdbfee11f395fdf8fdaedacfafbb555a1db9bd65/tiktok-images.ts\")\r\n\r\n```js\r\n// Name: tiktok-images\r\n// Description: Resize images to fit TikTok's 9:16 aspect ratio and avoid being covered by the UI\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Threads: trevor.atlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst sharp = await npm('sharp');\r\nconst { getAverageColor } = await npm('fast-average-color-node');\r\n\r\nconst width = 1440;\r\nconst height = 2400;\r\nconst density = 72;\r\nconst scale = .8;\r\nconst validTypes = new Set(['image/png', 'image/jpeg', 'image/jpg']);\r\nconst outputPath = path.join(home(), 'Desktop', 'resized-images');\r\n\r\nasync function processImage(imageFilepath: string) {\r\n try {\r\n const averageColor = await getAverageColor(imageFilepath);\r\n const image = await sharp(imageFilepath)\r\n .withMetadata({ density })\r\n .resize({ fit: 'inside', width: Math.floor(width * scale), height: Math.floor(height * scale) })\r\n .png({ quality: 100 })\r\n\r\n .toBuffer();\r\n\r\n const color = averageColor.hex || 'black';\r\n\r\n // Add a matching background\r\n const background = await sharp({\r\n create: {\r\n channels: 4,\r\n background: color,\r\n width,\r\n height,\r\n },\r\n })\r\n .withMetadata({ density })\r\n .png({ quality: 100})\r\n .toBuffer();\r\n\r\n\r\n const res = await sharp(background)\r\n .composite([{ input: image, gravity: 'centre' }])\r\n .png({ quality: 100 })\r\n .toBuffer();\r\n\r\n return res;\r\n } catch (error) {\r\n console.error(error);\r\n throw error;\r\n }\r\n};\r\n\r\ninterface FileInfo {\r\n lastModified: number;\r\n lastModifiedDate: string;//\"2023-07-12T17:35:13.573Z\"\r\n name: string;\r\n path: string;//\"/Users/uname/Desktop/screenshots/Screenshot 2022-01-12 at 1.35.08 PM.png\"\r\n size: number;\r\n type: string;//\"image/png\"\r\n webkitRelativePath: string;\r\n}\r\n\r\n\r\ntry {\r\n const fileInfos: FileInfo[] = await drop('Drop images to resize');\r\n const imagePaths = fileInfos\r\n .filter(({type}) => validTypes.has(type))\r\n .map(fileInfo => fileInfo.path);\r\n\r\n if (!imagePaths.length) {\r\n await notify('No valid images found. Supports .png, .jpg, and .jpeg');\r\n exit();\r\n }\r\n\r\n await ensureDir(outputPath);\r\n\r\n for (const imagePath of imagePaths) {\r\n const image = await processImage(imagePath);\r\n const [filename] = path.basename(imagePath).split('.');\r\n const finalPath = path.join(outputPath, `${filename}-processed.png`);\r\n await writeFile(finalPath, image);\r\n console.log(`Resized ${finalPath}`);\r\n }\r\n\r\n await notify('Image(s) resized');\r\n} catch (error) {\r\n console.error(error);\r\n await notify('Error resizing images. Check the log for details.');\r\n}\r\n\r\nawait open(outputPath);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1308","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","author":"Ivan Rybnikov","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1307","url":"https://github.com/johnlindquist/kit/discussions/1307","title":"Correct selection with ChatGPT","name":"Correct selection with ChatGPT","extension":".md","description":"Created by ivryb","resourcePath":"/johnlindquist/kit/discussions/1307","createdAt":"2023-07-12T17:02:06Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUlZi","body":"[Install \"Correct selection with ChatGPT\" to Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/9f63a1881b1827773682cdf7e404b05c/raw/9bce6cdf6dc54497f6d7020807fc1cc6bf405131/correct-selection.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1307","img":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1303","url":"https://github.com/johnlindquist/kit/discussions/1303","title":"Decode a base64 string","name":"Decode a base64 string","extension":".md","description":"Created by ElTacitos","resourcePath":"/johnlindquist/kit/discussions/1303","createdAt":"2023-07-10T10:09:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUjLC","body":"Everything is in the title of this post, this script will allow you to decode a base64 string.\r\n\r\n[Open base64-decode in Script Kit](https://scriptkit.com/api/new?name=base64-decode&url=https://gist.githubusercontent.com/ElTacitos/8eaa571a6026383c9ce71e593e31b598/raw/6aa4a043a4458b980111bfb76ebe0b435d90a524/base64-decode.js\")\r\n\r\n```js\r\n// Name: Decode Base64\r\n// Description: Decode a base64 string and copy it to the clipboard\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet base64 = await arg(\"Enter base64 string to decode\")\r\nlet decoded = atob(base64)\r\n\r\nawait clipboard.writeText(decoded)\r\nawait div(md(`\r\n# ${decoded}\r\n`))\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1303","img":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1296","url":"https://github.com/johnlindquist/kit/discussions/1296","title":"Replace user-defined acronyms with the full text","name":"Replace user-defined acronyms with the full text","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1296","createdAt":"2023-06-30T20:35:34Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUbye","body":"\r\n[Open de-acronym in Script Kit](https://scriptkit.com/api/new?name=de-acronym&url=https://gist.githubusercontent.com/trevor-atlas/992682a54fa4ec44ccc8cc58e889e026/raw/f4ec3016bd7f8b1af4be65a64f3d500c19231e71/de-acronym.ts\")\r\n\r\n```js\r\n// Menu: De-Acronym-ify\r\n// Description: Replace acronyms with their full names\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Shortcut: cmd ctrl opt shift a\r\n// Group: work\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nlet text = '';\r\nconst clipboardValue = await paste();\r\nconst selection = await getSelectedText();\r\n\r\nif (selection) {\r\n text = selection;\r\n console.log('use selection', selection);\r\n}\r\n\r\nif (clipboardValue && !selection) {\r\n text = clipboardValue;\r\n console.log('use clipboard', text);\r\n}\r\n\r\nif (!text) {\r\n text = await arg('Enter text to de-acronym-ify');\r\n console.log('use prompt', text);\r\n}\r\n\r\nconst acronyms: Array<[string | RegExp, string]> = [\r\n ['PD', 'Product Design'],\r\n ['PM', 'Product Management'],\r\n ['JS', 'JavaScript'],\r\n ['TS', 'TypeScript'],\r\n];\r\n\r\nconst result = acronyms.reduce(\r\n (acc, [acronym, expansion]) => acc.replace(acronym, expansion),\r\n text\r\n);\r\n\r\nif (!selection) {\r\n copy(result);\r\n} else {\r\n await setSelectedText(result);\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1296","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1295","url":"https://github.com/johnlindquist/kit/discussions/1295","title":"Type clipboard like a human","name":"Type clipboard like a human","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1295","createdAt":"2023-06-30T20:32:02Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUbyY","body":"I haven't quite dialed in the random delays as well as I'd like, but it gets the job done :]\r\n\r\n[Open humanlike-typing in Script Kit](https://scriptkit.com/api/new?name=humanlike-typing&url=https://gist.githubusercontent.com/trevor-atlas/17746a243dd9bbfa8062d8fb86b5fc20/raw/45b2a5cb769b76db73f70579edf28b469ba194bd/humanlike-typing.ts\")\r\n\r\n```js\r\n// Name: humanlike typing\r\n// Description: Type the contents of your clipboard as if you were a human\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\n\r\nawait applescript(String.raw`\r\nset texttowrite to the clipboard as text\r\ntell application \"System Events\"\r\n repeat with i from 1 to count characters of texttowrite\r\n if (character i of texttowrite) is equal to linefeed or (character i of texttowrite) is equal to return & linefeed or (character i of texttowrite) is equal to return then\r\n keystroke return\r\n else\r\n keystroke (character i of texttowrite)\r\n end\r\n if (character i of texttowrite) is equal to \" \" then\r\n delay (random number from 0.01 to 0.1)\r\n else if (character i of texttowrite) is equal to \"\\n\" then\r\n delay (random number from 0.1 to 0.3)\r\n else\r\n delay (random number from 0.01 to 0.05)\r\n end\r\n end repeat\r\nend tell\r\n`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1295","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1294","url":"https://github.com/johnlindquist/kit/discussions/1294","title":"Connect to GlobalProtect VPN if not connected","name":"Connect to GlobalProtect VPN if not connected","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1294","createdAt":"2023-06-30T19:36:57Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUbwy","body":"\r\n[Open vpn in Script Kit](https://scriptkit.com/api/new?name=vpn&url=https://gist.githubusercontent.com/trevor-atlas/45ea4ba63553e81facc93105cf52dc65/raw/a983e86ac4885afeff3928e268ad780020beffda/vpn.ts\")\r\n\r\n```js\r\n// Name: vpn\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Schedule: */15 * * * *\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\napplescript(`\r\ntell application \"System Events\" to tell process \"GlobalProtect\"\r\n\tset connectionStatus to get help of every menu bar item of menu bar 2\r\n\tif item 1 of connectionStatus = \"Not Connected\" then\r\n\t\tclick menu bar item 1 of menu bar 2 -- Activates the GlobalProtect \"window\" in the menubar\r\n\t\ttry\r\n\t\t\tclick button \"Connect\" of window 1\r\n\t\tend try\r\n\t\tclick menu bar item 1 of menu bar 2 -- This will close the GlobalProtect \"window\" after clicking Connect/Disconnect. This is optional.\r\n\tend if\r\nend tell\r\n`);\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1294","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1289","url":"https://github.com/johnlindquist/kit/discussions/1289","title":"Script Kit 1.76.10 - July 2023 Release - Searching Enhancements, Grouping Logic, More Choice Options","name":"Script Kit 1.76.10 - July 2023 Release - Searching Enhancements, Grouping Logic, More Choice Options","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1289","createdAt":"2023-06-28T00:11:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AUZGp","body":"# Script Kit 1.76.10 - July 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist\r\n\r\n## Features\r\n\r\n### Windows arm64 Build\r\n\r\nWe now have \"arm64\" builds for Windows: https://github.com/johnlindquist/kitapp/releases/tag/v1.76.10\r\n\r\n> arm64 Windows limitations: Unfortunately, the \"keyboard\" libraries for Windows are not yet available for arm64. So, you won't be able to use snippets, \"setSelectedText\", and other keyboard-related features. I'd recommend AutoHotkey as a workaround until the libraries are available.\r\n\r\n### Scripts Caching\r\n\r\nInstead of waiting for the main menu to parse/load the scripts, the previous scripts results are now cached. This shaves off the ~100ms delay when opening the main menu from the keyboard shortcut. (There are also some clever technical tricks under the hood that would require a blog post to explain)\r\n\r\n> Note: If you have a script that uses a shortcut which displays choices that don't change often, consider adding the `// Cache: true` metadata to the script for that extra 100ms boost.\r\n\r\n\r\n### Scripts Grouping and `groupChoices`\r\n\r\nUse the `//Group` metadata to group scripts together in the main menu. For example:\r\n\r\n```js\r\n// Group: Favorite\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png)\r\n\r\nThe you can search for \"Favorite\" from the menu to filter to the scripts with that metadata.\r\n\r\n\r\nYou can also add the `.group` property to any choice, then pass your choices to `groupChoices` to use the grouping behavior in your own scripts.\r\n\r\n```js\r\n// Name: Group Choices\r\n// Cache: true\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { faker } from \"@faker-js/faker\"\r\nimport _ from \"lodash\"\r\n\r\nlet faang = [\"Facebook\", \"Apple\", \"Amazon\", \"Netflix\", \"Google\"]\r\n\r\nlet people = Array.from({ length: 25 }).map(() => {\r\n let name = faker.person.fullName()\r\n return {\r\n name,\r\n value: name,\r\n description: faker.color.human(),\r\n group: _.sample(faang), // Grouping by a random company\r\n }\r\n})\r\n\r\nlet groupedChoices = groupChoices(people)\r\n\r\nlet result = await mini(\"Arg Group Demo\", groupedChoices)\r\nawait editor(result)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png)\r\n\r\n### Flags \"Submenu\"\r\n\r\nWhen using `flags`, pressing \"right\" (or `cmd+k`) now displays the available actions (AKA \"flags\") to the side in a separate searchable list with its own state.\r\n\r\nThis is a first pass at a \"submenu\" feature. We'll explore nested submenus in the future.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png)\r\n\r\n### Searching Off-loaded to Main Process\r\n\r\nPreviously, the searching logic was done in the renderer process. This creating some technical limitations on how we could pass results from a script to the list.\r\n\r\nNow, you will be able pass \"scored\" choice results directly from the script to the list enabling you move the search logic to your script/server/API and display highlighted \"scored\" choices in the list. More information on this in the future.\r\n\r\n### `// Pass` and `.pass` Metadata\r\n\r\nThe `// Pass` metadata on a script (and `.pass` property on a choice) will pass the current input of the prompt directly to the script so you can type input from the main menu that will be passed to the script.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png)\r\n\r\n```js\r\n// Name: Grocery List\r\n// Pass: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet note = arg?.pass || (await arg(\"Enter an item\"))\r\n\r\nawait editor(note)\r\n```\r\n\r\nYou can also do this with your own choices by using the `.pass` property on a choice and using the `groupChoices` (as discussed above) to bring up the more advanced list.\r\n\r\n### Choice `.miss` Property\r\n\r\nAdding `.miss` to a choice will make the choice only appear if the search doesn't match any other choices. This is useful for showing a \"No Results\" choice:\r\n\r\n```js\r\n// Name: Miss Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait mini(\"Select an Item\", [\r\n \"One\",\r\n \"Two\",\r\n {\r\n name: \"No Choices Available\",\r\n miss: true,\r\n disableSubmit: true,\r\n nameClassName: `text-red-500`,\r\n },\r\n])\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png)\r\n\r\n## Choice `.skip` Property\r\n\r\nUsing `.skip` allows you to create a choice that can't be searched/selected:\r\n\r\n> Note: This is used internally for the \"groupChoice()\" headers\r\n\r\n```js\r\n// Name: Separator Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"A demo for skip\", [\r\n \"Apple\",\r\n \"Banana\",\r\n \"Cherry\",\r\n {\r\n name: \"Separator\",\r\n height: PROMPT.ITEM.HEIGHT.XXXS,\r\n html: ``,\r\n skip: true,\r\n },\r\n \"Celery\",\r\n \"Cucumber\",\r\n \"Lettuce\",\r\n])\r\n```\r\n\r\n## Thanks for Using Script Kit!\r\n\r\nHappy Scripting!\r\n\r\n\\- John Lindquist","value":"https://github.com/johnlindquist/kit/discussions/1289","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1284","url":"https://github.com/johnlindquist/kit/discussions/1284","title":"Search Open PRs","name":"Search Open PRs","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1284","createdAt":"2023-06-21T20:59:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUT7K","body":"Change the owner and repo name to desired repo and get to reviewing!\r\n\r\n[Open search-open-pr in Script Kit](https://scriptkit.com/api/new?name=search-open-pr&url=https://gist.githubusercontent.com/mabry1985/7cf5cec8d5913948aeda070f51ecfe4d/raw/64c98abcade33cdeb54604f29a06c55f05d374f1/search-open-pr.js\")\r\n\r\n```js\r\n// Name: Search Open PRs\r\n// Description: Search open PRs in a repo\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst fetch = await npm(\"node-fetch\");\r\nconst variables = {\r\n owner: \"knapsack-labs\",\r\n repoName: \"app-monorepo\",\r\n};\r\n\r\nlet token = await env(\"GITHUB_AUTH_TOKEN\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst query = `\r\nquery getPrs($owner: String!, $repoName: String!) {\r\n repository(owner: $owner, name: $repoName) {\r\n pullRequests(last: 100, states: OPEN) {\r\n nodes {\r\n body\r\n createdAt\r\n mergeable\r\n number\r\n state\r\n title\r\n updatedAt\r\n url\r\n author {\r\n avatarUrl\r\n login\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n`;\r\n\r\nasync function getPrs() {\r\n return fetch(\"https://api.github.com/graphql\", {\r\n headers: {\r\n authorization: `bearer ${token}`,\r\n },\r\n method: \"POST\",\r\n body: JSON.stringify({ query, variables }),\r\n })\r\n .then((res) => res.json())\r\n .catch((err) => {\r\n console.log(err);\r\n exit();\r\n });\r\n}\r\nconst prs = await getPrs();\r\nconst openPRs = prs.data.repository.pullRequests.nodes;\r\nconst sortedPrs = openPRs.sort(\r\n (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)\r\n);\r\nconst pr = await arg(\r\n {\r\n placeholder: `Select a PR to view`,\r\n },\r\n sortedPrs.map((pr) => {\r\n return {\r\n name: `${pr.number} - ${pr.title}`,\r\n preview: () =>\r\n `
\r\n
${pr.number} - ${pr.title}
\r\n \r\n
Ready to Merge: ${pr.mergeable === \"MERGEABLE\" ? \"✅\" : \"⛔\"}
\r\n
${md(pr.body)}
\r\n \r\n
Author: ${pr.author.login}
\r\n \r\n \r\n
`,\r\n value: pr.number,\r\n };\r\n })\r\n);\r\n\r\nconst prInfo = sortedPrs.find((p) => p.number === pr);\r\nbrowse(prInfo.url);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1284","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1283","url":"https://github.com/johnlindquist/kit/discussions/1283","title":"A simple calculator using js expressions","name":"A simple calculator using js expressions","extension":".md","description":"Created by alkene0005","resourcePath":"/johnlindquist/kit/discussions/1283","createdAt":"2023-06-18T17:37:10Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AURMq","body":"\r\n[Open js-expression in Script Kit](https://scriptkit.com/api/new?name=js-expression&url=https://gist.githubusercontent.com/alkene0005/25bd8b0b560fdc4d1b582bf1c6a4ed55/raw/af15c0765269eb22b6aaa7ac207ed152c0088d3c/js-expression.js\")\r\n\r\n```js\r\n// Name: JS Expression\r\n// Description: I prefer to define it as a simple calculator\r\n\r\n// Global Objects\r\nlet arr = [1, 2, 3, 4, 5]\r\nlet obj = {name: 'Mike', age: 20}\r\n\r\n// Global Functions\r\nlet {\r\n ceil, floor, round, trunc, abs, PI,\r\n sin, cos, tan, log, log2, log10, exp, sqrt, cbrt, pow\r\n} = Math\r\n\r\n// Factorial\r\nlet fact = num => _.reduce(_.range(1, num + 1), (acc, i) => acc * i, 1)\r\n\r\nlet selected = await arg({\r\n placeholder: 'Expression ...',\r\n enter: 'Copy & Exit',\r\n shortcuts: [{\r\n name: 'Repalce', key: 'cmd+r', bar: 'right', onPress: async (input, {focused}) => {\r\n setInput(evalExp(input))\r\n }\r\n }]\r\n}, async (input) => {\r\n let res = input ? evalExp(input) : ''\r\n return md(`~~~javascript\\n${res}\\n~~~`)\r\n})\r\n\r\nif (selected) await copy(evalExp(selected))\r\n\r\nfunction evalExp(input) {\r\n let value = eval(`(${input})`)\r\n if (typeof value == 'number') return (value % 1 != 0 ? value.toFixed(2) : value) + ''\r\n if (typeof value == 'array') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'object') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'function') return ''\r\n}\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1283","img":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1281","url":"https://github.com/johnlindquist/kit/discussions/1281","title":"Quick Search Steam Game","name":"Quick Search Steam Game","extension":".md","description":"Created by alkene0005","resourcePath":"/johnlindquist/kit/discussions/1281","createdAt":"2023-06-18T03:49:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUQVl","body":"\r\n[Open web-steam-game in Script Kit](https://scriptkit.com/api/new?name=web-steam-game&url=https://gist.githubusercontent.com/alkene0005/17d2c82c34122a023841c09aae248800/raw/c666139ec1024fb0d3b1df3f92071e57d9713626/web-steam-game.js\")\r\n\r\n```js\r\n// Name: Steam\r\n\r\nimport axios from 'axios'\r\nimport cheerio from 'cheerio'\r\n\r\n// Language-dependent configuration\r\nconst cc = 'US'\r\nconst l = 'english'\r\n\r\nfunction buildResult(value, image, title) {\r\n return {\r\n name: 'abc',\r\n value: value,\r\n html: `\r\n
\r\n \r\n
${title}
\r\n
open
\r\n
\r\n `,\r\n }\r\n}\r\n\r\nlet url = await arg('Keyword ...', async keyword => {\r\n if (keyword.trim() == '') return []\r\n let {data} = await axios.get(\r\n 'https://store.steampowered.com/search/suggest?term=' + keyword +\r\n '&f=games&cc=' + cc + '&realm=1&l=s' + l + '&v=19040599&excluded_content_descriptors%5B%5D=3' +\r\n '&excluded_content_descriptors%5B%5D=4&use_store_query=1&use_search_spellcheck=1&search_creators_and_tags=1'\r\n );\r\n let $ = cheerio.load(data);\r\n let games = $('a').get().map(aTag => {\r\n if ($(aTag).hasClass('match_app')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let price = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${price}`)\r\n }\r\n if ($(aTag).hasClass('match_tag')) {\r\n let name = $(aTag).find('.match_name span').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, 'https://pbs.twimg.com/profile_images/861662902780018688/SFie8jER_x96.jpg', `${name} - ${count}`)\r\n }\r\n if ($(aTag).hasClass('match_creator')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${count}`)\r\n }\r\n });\r\n return games.filter(x => x);\r\n});\r\n\r\nawait $`open ${url}`\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1281","img":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1272","url":"https://github.com/johnlindquist/kit/discussions/1272","title":"Google PaLM2 Chat","name":"Google PaLM2 Chat","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1272","createdAt":"2023-06-06T01:54:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUGVn","body":"The LLM is still in early access but you can sign up for the waitlist [here](https://developers.generativeai.google/)\r\n\r\n[Open palm-chat in Script Kit](https://scriptkit.com/api/new?name=palm-chat&url=https://gist.githubusercontent.com/mabry1985/54c10fa4594fb5a5edcf65c1db55b44b/raw/8460dafe391bd8bb09593e35e2fb89764d27f521/palm-chat.js\")\r\n\r\n```js\r\nlet { GoogleAuth } = await import(\"google-auth-library\");\r\nlet { DiscussServiceClient } = await import(\"@google-ai/generativelanguage\");\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst MODEL_NAME = \"models/chat-bison-001\";\r\nconst API_KEY = await env(\"PALM_API_KEY\", {\r\n hint: `Signup for waitlist here here`,\r\n});\r\n\r\nconst client = new DiscussServiceClient({\r\n authClient: new GoogleAuth().fromAPIKey(API_KEY),\r\n});\r\n\r\nconst config = {\r\n model: MODEL_NAME,\r\n temperature: 0.75,\r\n candidateCount: 1,\r\n top_k: 40,\r\n top_p: 0.95,\r\n};\r\n\r\nconst chatHistory = [];\r\n\r\nconst generateText = async (text) => {\r\n chatHistory.push({ content: text });\r\n const response = await client.generateMessage({\r\n ...config,\r\n prompt: {\r\n context: \"You are a funny and helpful assistant.\",\r\n messages: chatHistory,\r\n },\r\n });\r\n\r\n log(response);\r\n log(response[0].filters);\r\n if (response[0].filters.length > 0) {\r\n return `The model has rejected your input. Reason: ${response[0].filters[0].reason}`;\r\n } else {\r\n chatHistory.push({ content: response[0].candidates[0].content });\r\n return response[0].candidates[0].content;\r\n }\r\n};\r\n\r\nawait chat({\r\n onSubmit: async (input) => {\r\n setLoading(true);\r\n try {\r\n const response = await generateText(input);\r\n let message = md(response);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, message);\r\n } catch (e) {\r\n console.log(e);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, md(\"Error: \" + e.message));\r\n }\r\n setLoading(false);\r\n },\r\n});\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1272","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1271","url":"https://github.com/johnlindquist/kit/discussions/1271","title":"Static to dynamic import converter","name":"Static to dynamic import converter","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1271","createdAt":"2023-06-04T18:07:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUFWE","body":"I got tired of typing out the conversion when pulling in script examples so I made this quick script to convert them\r\n\r\n[Open static-to-dynamic in Script Kit](https://scriptkit.com/api/new?name=static-to-dynamic&url=https://gist.githubusercontent.com/mabry1985/13b951630f05eebc35c66d8e706dee70/raw/70fb4529876ef97fd18351793d329afca945079e/static-to-dynamic.js\")\r\n\r\n```js\r\n// Name: Static to Dynamic\r\n// Description: Convert static import to dynamic import\r\n// e.g. import { Foo } from \"bar\";\r\n// to let { Foo } = await import(\"bar\");\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst text = await getSelectedText();\r\n\r\nfunction convertImportString(input) {\r\n const importRegex = /import\\s+({[^}]+})\\s+from\\s+\"([^\"]+)\";/;\r\n\r\n if (!importRegex.test(input)) {\r\n throw new Error(\"Invalid import string format\");\r\n }\r\n\r\n const [_, importList, modulePath] = input.match(importRegex);\r\n const output = `let ${importList} = await import(\"${modulePath}\");`;\r\n return output;\r\n}\r\n\r\nconst output = convertImportString(text);\r\n\r\nawait setSelectedText(output);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1271","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","author":"Rohit Kumar Saini","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1262","url":"https://github.com/johnlindquist/kit/discussions/1262","title":"A simple Password Manager","name":"A simple Password Manager","extension":".md","description":"Created by rockingrohit9639","resourcePath":"/johnlindquist/kit/discussions/1262","createdAt":"2023-05-21T04:44:25Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AT5eR","body":"# Password Manager\r\nA simple password manager to add new passwords and copy from one of the existing list of passwords. Passwords are saved after encryption.\r\n\r\n\r\n\r\n[Open password-manager in Script Kit](https://scriptkit.com/api/new?name=password-manager&url=https://gist.githubusercontent.com/rockingrohit9639/586628e63330061cdeaff35cbc7dec05/raw/231215f7064ce763ffd4b2aa93ebb00c0341f080/password-manager.ts\")\r\n\r\n\r\n```js\r\n// Menu: Password Manager\r\n// Description: Manager all your passwords justing using few keys\r\n// Shortcut: command shift ]\r\n// Author: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst { nanoid } = await npm(\"nanoid\");\r\nconst Cryptr = await npm(\"cryptr\");\r\n\r\nconst CRYPTR_KEY = await env(\"CRYPTR_KEY\");\r\nconst cryptr = new Cryptr(CRYPTR_KEY);\r\n\r\nconst { passwords, write } = await db(\"passwords\", { passwords: [] });\r\n\r\ntype Option = {\r\n name: string;\r\n description: string;\r\n value: \"ADD_NEW_PASSWORD\" | \"COPY_PASSWORD\";\r\n};\r\n\r\nconst PM_OPTIONS: Option[] = [\r\n {\r\n name: \"Add New Password\",\r\n description: \"Add a new password to the database\",\r\n value: \"ADD_NEW_PASSWORD\",\r\n },\r\n {\r\n name: \"Copy Password\",\r\n description: \"Copy one of the saved passwords\",\r\n value: \"COPY_PASSWORD\",\r\n },\r\n];\r\n\r\nconst choice: Option[\"value\"] = await arg(\r\n \"What would you like to do?\",\r\n PM_OPTIONS\r\n);\r\n\r\n/** Doing operation on basis of choice */\r\nif (choice === \"ADD_NEW_PASSWORD\") {\r\n addNewPassword();\r\n}\r\n\r\nif (choice === \"COPY_PASSWORD\") {\r\n listAndCopyPassword();\r\n}\r\n\r\nasync function addNewPassword() {\r\n const title = await arg({\r\n placeholder: \"Title\",\r\n hint: \"Title for which your password belongs e.g Facebook etc.\",\r\n });\r\n const password = await arg({\r\n placeholder: \"Password\",\r\n hint: `Password you want to save for ${title}`,\r\n });\r\n\r\n /** Encrypting the password */\r\n const encryptedPassword = cryptr.encrypt(password);\r\n\r\n const id = nanoid(5);\r\n const newPassword = { id, title, password: encryptedPassword };\r\n passwords.push(newPassword);\r\n\r\n /** Saving the password in db */\r\n await write();\r\n notify(`Password for ${title} added successfully!`);\r\n}\r\n\r\nasync function listAndCopyPassword() {\r\n const passwordToCopy = await arg(\r\n \"Which password would you like to copy ?\",\r\n () =>\r\n passwords.map(({ title, password }) => ({ name: title, value: password }))\r\n );\r\n\r\n /** Decrypting the password */\r\n const decryptedPassword = cryptr.decrypt(passwordToCopy);\r\n\r\n /** Copying the password to clipboard */\r\n copy(decryptedPassword);\r\n notify(\"Password copied to you clipboard!\");\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1262","img":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/597015?u=1e764f789e382da7c81ee37ef3e1060bac244310&v=4","user":"LukeCarrier","author":"Luke Carrier","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1261","url":"https://github.com/johnlindquist/kit/discussions/1261","title":"Silly Pomodoro timer","name":"Silly Pomodoro timer","extension":".md","description":"Created by LukeCarrier","resourcePath":"/johnlindquist/kit/discussions/1261","createdAt":"2023-05-20T19:31:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AT5VI","body":"Just a small hack to replace the many menu bar applications I've used over the years, and an excuse to have a play with Script Kit. I'm glad I did -- it's awesome 😁 \r\n\r\n[Open pomodoro in Script Kit](https://scriptkit.com/api/new?name=pomodoro&url=https://gist.githubusercontent.com/LukeCarrier/b5800f573f43fc7acf4ea327f6e396b4/raw/d75fe7d8b4428500457cb2e6de3e2b11e1c9353c/pomodoro.ts\")\r\n\r\n```js\r\n// Name: Pomodoro\r\n// Description: A Pomodoro timer, right here!\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst HOUR_MIN = 60;\r\nconst MIN_SEC = 60;\r\nconst SEC_MS = 1000;\r\n\r\nconst WORK_INTERVAL_SECS = 25 * 60;\r\nconst REST_INTERVAL_SECS = 5 * 60;\r\n\r\nconst WORK_INTERVAL_ICON = \"🍅\";\r\nconst REST_INTERVAL_ICON = \"🏝️\";\r\nconst COMPLETE_ICON = \"🎉\";\r\n\r\nconst WIDGET_HTML = `\r\n
\r\n {{icon}}\r\n
\r\n
\r\n
{{goal}}
\r\n
{{timer}}
\r\n
\r\n`;\r\nconst DING_JS = `new Audio(\"../kenvs/personal/assets/ding.ogg\").play();`;\r\nconst DING_SECS = 5;\r\n\r\nfunction formatTimeRemaining(seconds: number): string {\r\n const totalMinutes = Math.floor(seconds / HOUR_MIN);\r\n const formatSeconds = String(seconds % MIN_SEC).padStart(2, \"0\");\r\n const formatMinutes = String(totalMinutes % MIN_SEC).padStart(2, \"0\");\r\n return `${formatMinutes}:${formatSeconds}`;\r\n}\r\n\r\nconst goal = await arg(\"What's your goal this interval?\")\r\n\r\nconst timerWidget = await widget(WIDGET_HTML, {\r\n title: \"Pomodoro\",\r\n state: { icon: \"\", goal: \"\", timer: \"\" },\r\n\r\n containerClass: \"p-6 max-w-sm mx-auto rounded-xl shadow-lg flex items-center space-x-4\",\r\n alwaysOnTop: true,\r\n preventEscape: true,\r\n minimizable: false,\r\n maximizable: false,\r\n fullscreenable: false,\r\n opacity: 0.45,\r\n\r\n // If these are below the minimum size of a widget on macOS (160x120) the\r\n // widget appears as a small white box without any content until manually\r\n // resized.\r\n width: 340,\r\n height: 120,\r\n});\r\n\r\nfunction doInterval(icon: string, goal: string, interval_secs: number): Promise {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(interval_secs) });\r\n\r\n return new Promise((resolve) => {\r\n const startTime = new Date().getTime();\r\n const timerInterval = setInterval(() => {\r\n const thisTime = new Date().getTime();\r\n const elapsedSeconds = Math.round((thisTime - startTime) / SEC_MS);\r\n const remainingSeconds = interval_secs - elapsedSeconds;\r\n if (remainingSeconds >= 0) {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(remainingSeconds) });\r\n } else {\r\n clearInterval(timerInterval);\r\n timerWidget.executeJavaScript(DING_JS).finally(() => {\r\n resolve();\r\n });\r\n }\r\n }, 1000);\r\n });\r\n}\r\n\r\nawait doInterval(WORK_INTERVAL_ICON, goal, WORK_INTERVAL_SECS);\r\nawait doInterval(REST_INTERVAL_ICON, `Break after ${goal}`, REST_INTERVAL_SECS);\r\ntimerWidget.setState({ icon: COMPLETE_ICON, goal: `${goal} all done!`, timer: \"That's another interval complete.\" });\r\nsetTimeout(() => timerWidget.close(), DING_SECS * 1000);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1261","img":"https://avatars.githubusercontent.com/u/597015?u=1e764f789e382da7c81ee37ef3e1060bac244310&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/8198764?u=8159fd937f0836c330f0aed6c4d518c48461072e&v=4","user":"Schmedu","author":"Eddie","twitter":"schmedu_","discussion":"https://github.com/johnlindquist/kit/discussions/1259","url":"https://github.com/johnlindquist/kit/discussions/1259","title":"JSON 2 YAML","name":"JSON 2 YAML","extension":".md","description":"Created by Schmedu","resourcePath":"/johnlindquist/kit/discussions/1259","createdAt":"2023-05-17T10:31:51Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AT2vb","body":"[Open json2yaml in Script Kit](https://scriptkit.com/api/new?name=json2yaml&url=https://gist.githubusercontent.com/Schmedu/c904124d7a9cd4b9fd25485c9d8c36d0/raw/75255898c5293cbe648e1f7c521bc5a93c120e7b/json2yaml.ts\")\r\n\r\n```js\r\n// Name: Json To Yaml Converter\r\n// Author: Eduard Uffelmann\r\n// Twitter: @schmedu_\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport * as yaml from \"js-yaml\";\r\n\r\nlet filePath = await getSelectedFile();\r\nlet content = await readJson(filePath);\r\n\r\nlet result = yaml.dump(content);\r\n\r\nlet todo = await mini(\"What to do?\", [\"Copy\", \"Save\"]);\r\nif (todo === \"Copy\") {\r\n await copy(result);\r\n} else {\r\n await writeFile(filePath.replace(\".json\", \".yaml\"), result);\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1259","img":"https://avatars.githubusercontent.com/u/8198764?u=8159fd937f0836c330f0aed6c4d518c48461072e&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1256","url":"https://github.com/johnlindquist/kit/discussions/1256","title":"Script Kit 1.59.1 - May 2023 Release","name":"Script Kit 1.59.1 - May 2023 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1256","createdAt":"2023-05-15T18:49:05Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AT1QW","body":"# Script Kit 1.59.1 - May 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Sunsetting Features\r\n\r\nWe're \"sunsetting\"/not supporting a few features going forward. \r\n\r\n> The `npm` and `db` apis will still be available (they're used internally in the SDK!), but we won't be actively supporting them\r\n\r\n1. `await npm()`. Use `await import()` or regular `import` statements instead.\r\n\r\nScript Kit now recommends using standard import methods. When you attempt to run a script and an import fails, Script Kit will catch the error and prompt you to install it. This removes the need for `await npm()`. This is especially useful for people using TypeScript as you don't need to worry about typing `npm` anymore.\r\n\r\n2. `await db()`. We now recommend `keyv`: [https://www.npmjs.com/package/keyv](https://www.npmjs.com/package/keyv)\r\n\r\n`keyv` is a much better solution for storing/retrieving data and achieves all of the future features we had planned for `db`.\r\n\r\nWe'll do our best to update older demos/tutorials that use these methods as we're able.\r\n\r\n## Previews Everywhere\r\n\r\nEvery prompt type (`term`, `drop`, `fields`, etc) now supports the `preview` key. Pass in HTML to display it to the right side of your prompt. This is great for instructions and guiding the user through each script:\r\n\r\n> Note: The `md()` helper is often used to transform markdown into HTML\r\n\r\n\r\n```js\r\nawait term({\r\n preview: md(`# Follow these steps:\r\n\r\n1. First, type \\`ls\\` to see the files in this directory\r\n2. Then, type \\`cat \\` to see the contents of a file\r\n3. Finally, type \\`exit\\` to exit the terminal\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n```js\r\nawait drop({\r\n placeholder: \"Drop an mp3\",\r\n preview: md(`# Convert mp3 to wav\r\n\r\n## Instructions\r\n\r\n1. Drag and drop an mp3 file\r\n2. A progress prompt will open then close when ready\r\n3. Your .wav will be created next to the .mp3 file\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n## `term.write()`\r\n\r\nWhen a `term` prompt is open, you can issue terminal commands from shortcuts, etc by using `term.write(myCommand)`\r\n\r\n```js\r\nawait term({\r\n shortcuts: [\r\n {\r\n name: \"List files\",\r\n key: `${cmd}+l`,\r\n onPress: () => {\r\n let command = isWin ? \"dir\" : \"ls\"\r\n term.write(command)\r\n },\r\n bar: \"right\",\r\n },\r\n {\r\n name: \"Up a Directory\",\r\n key: `${cmd}+u`,\r\n onPress: () => {\r\n term.write(\"cd ..\")\r\n },\r\n bar: \"left\",\r\n },\r\n {\r\n name: \"Clear\",\r\n key: `${cmd}+k`,\r\n onPress: () => {\r\n let command = isWin ? \"cls\" : \"clear\"\r\n term.write(command)\r\n },\r\n bar: \"left\",\r\n },\r\n ],\r\n})\r\n```\r\n\r\n\r\n## Kenv Improvements:\r\n\r\n### \"New Kenv\" prompt for GitHub repo:\r\n\r\nWhen creating a new Kenv, it will guide you through the process of linking the Kenv to a remote GitHub repo:\r\n\r\n\r\n\r\n\r\n### With a script selected, take Kenv actions\r\n\r\nPress \"right\" (or `cmd+k`) with a script selected to reveal many \"Kenv\" actions:\r\n\r\n\r\n\r\n### Push/Pull From Remote Kenv\r\n\r\nFrom a script (or a \"Manage Kenv\"), you can now push/pull changes as it swaps you over to a terminal to take action:\r\n\r\n\r\n\r\n### \"Trusted\" Kenvs\r\n\r\nThanks to Script Kit + AI integrations, we've had a large influx of \"non-developer\" users. This necessitated more warnings/protections around sharing scripts.\r\n\r\nSome scripts have features that run scripts automatically: Shortcuts, schedule, background, etc. These kenvs now need to be \"Trusted\" to enable these features to add an extra layer of protection against bad actors. PLEASE only use scripts from people you absolutely trust.\r\n\r\nYou can \"trust\" a kenv during new/clone setup, or later from the Kit tab->Manage Kenv menu. You can also \"untrust\" a kenv from the same menu.\r\n\r\n\r\n\r\n## \"Trigger\" flag\r\n\r\nIf a script is run by \"schedule\", \"shortcut\", etc, you can now access what triggered it by using\r\n\r\n```js\r\nif(flag?.trigger === \"schedule\") // do something specific\r\n```\r\n\r\nThis will allow you customize the behavior based on whether you invoked it manually or automatically\r\n\r\n## Windows Fixes\r\n\r\nHandled edge-cases around\r\n\r\n- Windows setup/install process\r\n- Windows usernames that include spaces\r\n- Windows terminal fixes\r\n\r\nThanks to all the Windows bug reports and testers on Discord. Please keep them coming ❤️\r\n\r\n## Node 18.15.0\r\n\r\nWe're now on node 18.15.0. View the CHANGELOG: https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V18.md#18.15.0\r\n\r\nHappy Scripting - John Lindquist","value":"https://github.com/johnlindquist/kit/discussions/1256","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1254","url":"https://github.com/johnlindquist/kit/discussions/1254","title":"Get GitHub Commit Messages Since Tag","name":"Get GitHub Commit Messages Since Tag","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1254","createdAt":"2023-05-10T16:08:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATxIs","body":"\r\n[Open get-commits in Script Kit](https://scriptkit.com/api/new?name=get-commits&url=https://gist.githubusercontent.com/johnlindquist/e56b9ad663cd56c947cc528c5f1c9f96/raw/a10ac8f6d49ebf961fd08013aec4f9b998e1024c/get-commits.ts)\r\n\r\n```js\r\n// Name: Get GitHub Commits Messages Since Tag\r\n// Description: Get all commit messages since a tag\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Octokit } = await import(\"@octokit/rest\")\r\n\r\nlet ownerRepo = await arg(\"Enter username/repo. Example: johnlindquist/kit\")\r\nlet [owner, repo] = ownerRepo.split(\"/\")\r\nlet tag = await arg(\"Tag. Example: v1.54.53\")\r\n\r\nlet client = new Octokit({\r\n auth: await env(\"GITHUB_PERSONAL_ACCESS_TOKEN\"),\r\n})\r\n\r\nlet page = 1\r\nlet hasMorePages = true\r\nlet messages = []\r\n\r\nlet ref = null\r\nlet tagPage = 1\r\nwhile (!ref) {\r\n let listTags = await client.repos.listTags({\r\n owner,\r\n repo,\r\n per_page: 100,\r\n name: tag,\r\n page: tagPage,\r\n })\r\n\r\n tagPage++\r\n ref = listTags.data.find(t => t.name === tag).commit.sha\r\n}\r\n\r\nlet commit = await client.repos.getCommit({\r\n owner,\r\n repo,\r\n ref,\r\n})\r\n\r\nlet since = commit.data.commit.author.date\r\n\r\nwhile (hasMorePages) {\r\n let data = await client.repos.listCommits({\r\n owner,\r\n repo,\r\n since,\r\n per_page: 100,\r\n page: page,\r\n })\r\n\r\n hasMorePages = data.data.length === 100\r\n messages = messages.concat(data.data.map(c => c.commit.message))\r\n\r\n page++\r\n}\r\n\r\nlet text = messages.join(\"\\n\\n\")\r\n\r\nif (env?.[\"GITHUB_SCRIPTKIT_TOKEN\"]) {\r\n let response = await createGist(text, {\r\n description: `Commit messages since ${tag}`,\r\n isPublic: false,\r\n fileName: \"commit-messages.txt\",\r\n })\r\n\r\n open(response.html_url)\r\n\r\n debugger\r\n} else {\r\n await editor(text)\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1254","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5610359?u=7019998d8df8daf3c7dda50ab3682fce7d43aad2&v=4","user":"pierre-borckmans","author":"Pierre Borckmans","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1253","url":"https://github.com/johnlindquist/kit/discussions/1253","title":"Drive homebrew through Script kit","name":"Drive homebrew through Script kit","extension":".md","description":"Created by pierre-borckmans","resourcePath":"/johnlindquist/kit/discussions/1253","createdAt":"2023-05-10T14:31:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATxC2","body":"Here's a small script that lets the user drive Homebrew from kit script:\r\n- list installed formulae / casks\r\n- install a new formula / cask from a list of all the ones available, minus the ones already installed\r\n- uninstall an existing formula/cask\r\n\r\nHope it's useful\r\n\r\n```// Name: Homebrew menu\r\n// Description: Drive homebrew through Kit-script\r\n// Author: Pierre Borckmans\r\n\r\n// shortcut: ctrl opt cmd b\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst BREW_CMD = '/opt/homebrew/bin/brew'\r\n\r\n\r\nconst cmdList = async (cmd) => (await cmd)._stdout.split(\"\\n\").slice(0, -1)\r\n\r\n\r\nconst getInstalled = async (type) => {\r\n return await cmdList($`${BREW_CMD} list --${type} -1`)\r\n}\r\n\r\nconst getAvailable = async (type) => {\r\n const items = (await cmdList($`${BREW_CMD} ${type} -1`)).map(o => ({\r\n name: o,\r\n value: o,\r\n description: type.slice(0, -1)\r\n }))\r\n const alreadyInstalled = await getInstalled(type.slice(0, -1))\r\n return items.filter(i => !alreadyInstalled.find(ai => ai === i.value));\r\n}\r\n\r\nconst install = async () => {\r\n const packageName = await arg(\"Enter a package to install\", [...await getAvailable(\"formulae\"), ...await getAvailable(\"casks\")])\r\n await $`${BREW_CMD} install ${packageName}`\r\n}\r\n\r\nconst uninstall = async () => {\r\n const packageName = await arg(\"Choose a package to uninstall\", [...await list(\"formula\"), ...await list(\"cask\")])\r\n await $`${BREW_CMD} uninstall ${packageName}`\r\n}\r\n\r\nconst menu = async () => {\r\n const menuOptions = [\r\n { \r\n value: \"formulae\",\r\n name: \"List installed formulae\"\r\n },\r\n { \r\n value: \"casks\",\r\n name: \"List installed casks\"\r\n },\r\n { \r\n value: \"install\",\r\n name: \"Install a cask or formula\"\r\n },\r\n { \r\n value: \"uninstall\",\r\n name: \"Uninstall a cask or formula\"\r\n },\r\n ]\r\n const menuChoice = await arg(\"Select an option\", menuOptions)\r\n\r\n switch (menuChoice) {\r\n case \"formulae\":\r\n await arg(`Homebrew formulas`, await getInstalled(\"formula\"))\r\n break\r\n case \"casks\":\r\n await arg(`Homebrew casks`, await getInstalled(\"cask\"))\r\n break\r\n case \"install\":\r\n await install()\r\n break \r\n case \"uninstall\":\r\n await uninstall()\r\n break \r\n default:\r\n break;\r\n }\r\n await menu()\r\n}\r\n\r\nawait menu();\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1253","img":"https://avatars.githubusercontent.com/u/5610359?u=7019998d8df8daf3c7dda50ab3682fce7d43aad2&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/22137371?u=22b3858822ffbf8ddce68b5c2e000a9910dbb934&v=4","user":"ScytheDraven47","author":"Ben Rogers-McKee","twitter":"ScytheDraven47","discussion":"https://github.com/johnlindquist/kit/discussions/1251","url":"https://github.com/johnlindquist/kit/discussions/1251","title":"Bitwarden Passwords via CLI","name":"Bitwarden Passwords via CLI","extension":".md","description":"Created by ScytheDraven47","resourcePath":"/johnlindquist/kit/discussions/1251","createdAt":"2023-05-09T08:45:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATv44","body":"I figured it'd be nice to have a Bitwarden Script for use outside of browsers, and it made for a good first mini project.\r\nIt uses [@bitwarden/cli](https://www.npmjs.com/package/@bitwarden/cli) via NPM, though I'm looking at doing an API version as well.\r\n\r\n[Code/gist here](https://gist.github.com/ScytheDraven47/0605ea9475778ae9cc2279c6fd07ad2e)\r\n\r\n`Enter` copies password\r\n`Ctrl+Enter` copies username\r\n`Ctrl+Shift+Enter` pastes username, then tabs once, then pastes password (won't work for all use cases, but figured it's nice to have)\r\n\r\nThis script does not save user credentials, but saves a session key to prevent frequent logging in.\r\n\r\nCurrently missing 2FA via email and YubiKey.","value":"https://github.com/johnlindquist/kit/discussions/1251","img":"https://avatars.githubusercontent.com/u/22137371?u=22b3858822ffbf8ddce68b5c2e000a9910dbb934&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1249","url":"https://github.com/johnlindquist/kit/discussions/1249","title":"Multichoice for the `arg` function","name":"Multichoice for the `arg` function","extension":".md","description":"Created by BeSpunky","resourcePath":"/johnlindquist/kit/discussions/1249","createdAt":"2023-05-07T20:29:14Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATuj1","body":"I wrapped the `arg` function to allow multi-selection.\r\nJust replace `arg` with `multiArg` and you're good to go. ✨😊\r\nProvide a 3rd argument to customize item templates.\r\n\r\n[See code here](https://gist.github.com/BeSpunky/468b2e790ba9e32a73a3717dc876bdc4)\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236700932-ad5410fa-f717-4dea-9657-b78a0fc4a6c5.mp4\r\n\r\n`Enter`: Toggles Selection\r\n`Ctrl+Enter`: Submits the results\r\n\r\n**Known issues:**\r\n* When the list is longer than the window, list jumps occur. See #1248 \r\n* The `input` parameter passed into the choice factory function (2nd argument of `arg`) is always `''` and doesn't reflect user input.\r\n* List is filtered, but the default template doesn't highlight fuzzy search matches like the original one.\r\n\r\nEnjoy 🥂","value":"https://github.com/johnlindquist/kit/discussions/1249","img":"https://avatars.githubusercontent.com/u/47897671?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1247","url":"https://github.com/johnlindquist/kit/discussions/1247","title":"Efficient rebuild of scripts when `lib` files change","name":"Efficient rebuild of scripts when `lib` files change","extension":".md","description":"Created by BeSpunky","resourcePath":"/johnlindquist/kit/discussions/1247","createdAt":"2023-05-05T21:38:13Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATtIs","body":"This script watches the `lib` folder, and when changes to `ts` files are made, it does 2 things:\r\n1. Create/update a dependency graph of `libFilePath -> dependantScriptPaths[]`\r\n2. Touches all script files that depend on the changed `lib` file to trigger rebuild.\r\n\r\nhttps://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a\r\n\r\n**TLDR**\r\nCurrently, ScriptKit only rebuilds scripts if it detects changes to the `scripts` folder.\r\nIf you extract your reusable parts and put them into the `lib` folder, ScriptKit doesn't pick up on changes to those files.\r\nThe manual way to overcome this is to save your script file again and trigger rebuild.\r\n\r\nNo more... :)\r\n\r\nActually, this is a 3 scripts solution:\r\n1. [`update-script-dependency-graphs`](https://gist.github.com/BeSpunky/c9139cdedfa349c501a70febea3c46d5): Partially rebuilds the graph if a triggering file has been provided, otherwise completely rebuilds it.\r\n2. [`watch-libs`](https://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a): Watches lib files, partially rebuild the graph using `update-script-dependency-graph`, then touch the scripts.\r\n3. [`watch-scripts`](https://gist.github.com/BeSpunky/1ce1ad4e29b339bec11cd2d416cb676c): Watch script files, partially rebuild the graph\r\n","value":"https://github.com/johnlindquist/kit/discussions/1247","img":"https://avatars.githubusercontent.com/u/47897671?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1246","url":"https://github.com/johnlindquist/kit/discussions/1246","title":"Create a Gist for you script and it's lib dependencies","name":"Create a Gist for you script and it's lib dependencies","extension":".md","description":"Created by BeSpunky","resourcePath":"/johnlindquist/kit/discussions/1246","createdAt":"2023-05-05T21:23:15Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATtIP","body":"## Watch how I publish the script that publishes scripts and their dependencies to Gist... 😄\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236570763-9c84163f-c4f3-46d6-943f-537724db2b2e.mp4\r\n\r\n\r\nHere's the Gist of it:\r\nhttps://gist.github.com/BeSpunky/ff5dcb62887cbee686dd6c3ba31cabb5\r\n\r\n**TLDR**\r\nAs I go playing with ScriptKit, I started using the `lib` folder to centralize reusable functionality.\r\nThis made my scripts difficult to share, as they have nested dependencies which I would've had to add manually to my Gists.\r\nWell no more... 💪\r\n\r\nThis script let's you choose one of your scripts, reads it and recursively extracts `lib` dependencies, then publishes a new gist with the script and the dependencies.\r\n\r\nEnjoy 😊","value":"https://github.com/johnlindquist/kit/discussions/1246","img":"https://avatars.githubusercontent.com/u/47897671?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1245","url":"https://github.com/johnlindquist/kit/discussions/1245","title":"Prompt Anywhere v2","name":"Prompt Anywhere v2","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1245","createdAt":"2023-05-05T08:07:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATsa1","body":"[Open prompt-anywhere in Script Kit](https://scriptkit.com/api/new?name=prompt-anywhere&url=https://gist.githubusercontent.com/mabry1985/482fcf46ae66d79348d63096e00fb5d5/raw/2114b2c4644787ec77ae9f39adff333ecc23f864/prompt-anywhere.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as quick-prompt.js and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n //#########\r\n // Helpers\r\n //########\r\n // exit script on cancel\r\n const cancelChat = () => {\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Paste text to highlighted text and exit script\r\n * @param {*} text\r\n */\r\n const pasteTextAndExit = async (text) => {\r\n await setSelectedText(text);\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Copy text to clipboard and exit script\r\n * @param {*} text\r\n */\r\n const copyToClipboardAndExit = async (text) => {\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n };\r\n\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // which works, but would be nice to also have ESC work\r\n ignoreBlur: false,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // have paste on text on submit?\r\n // onSubmit: () => pasteTextAndExit(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n await pasteTextAndExit(currentMessage);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // @TODO still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all of the actions like copy, paste, etc\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => await copyToClipboardAndExit(state),\r\n onSubmit: async (state) => await pasteTextAndExit(state),\r\n });\r\n break;\r\n case \"copy\":\r\n await copyToClipboardAndExit(currentMessage);\r\n case \"save\":\r\n await inspect(currentMessage, `/conversations/${Date.now()}.md`);\r\n exitChat();\r\n default:\r\n copyToClipboardAndExit(currentMessage);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1245","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1244","url":"https://github.com/johnlindquist/kit/discussions/1244","title":"Prompt Anywhere v2","name":"Prompt Anywhere v2","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1244","createdAt":"2023-05-05T06:30:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATsW5","body":"\r\n[Open prompt-anything in Script Kit](https://scriptkit.com/api/new?name=prompt-anything&url=https://gist.githubusercontent.com/mabry1985/6c2412d4d3d1360276b9d95f44548815/raw/1dac5e149ebc4cf86ea830db9104d8518f47eca5/prompt-anything.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as `quick-prompt.js` and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n//#########\r\n// Helpers\r\n//########\r\n// exit script on cancel\r\nconst cancelChat = () => {\r\n process.exit(1);\r\n};\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage + options),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // ignoreBlur: true,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // onSubmit: () => setSelectedText(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n // paste into highlighted text\r\n await setSelectedText(currentMessage);\r\n process.exit(1);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all these same options such as save\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => {\r\n // copy to clipboard when exiting the editor\r\n await clipboard.writeText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n onSubmit: async (state) => {\r\n // paste into highlighted text when pressing enter\r\n await setSelectedText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n });\r\n break;\r\n case \"copy\":\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n // exit script\r\n process.exit(1);\r\n case \"save\":\r\n await inspect(currentMessage, `conversations/${Date.now()}.md`);\r\n // exit script\r\n process.exit(1);\r\n default:\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1244","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/629240?u=4750610206bd5ebafbdfab85d5d9c81dc8ce21ed&v=4","user":"blakecannell","author":"Blake.","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1240","url":"https://github.com/johnlindquist/kit/discussions/1240","title":"Snippets Manager (File Based)","name":"Snippets Manager (File Based)","extension":".md","description":"Created by blakecannell","resourcePath":"/johnlindquist/kit/discussions/1240","createdAt":"2023-05-04T04:40:53Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATrcB","body":"Simple file based snippets manager:\r\n\r\nA few things to note:\r\n- File paths are currently specific to Windows. I will update for other envionments.\r\n- This assumes a `snippets` folder exists within your home folder. Is there any way to make this configurable (within the manager itself)? This is of course quite simple to set as a constant at the top of the file if not.\r\n\r\nhttps://gist.github.com/blakecannell/bea79f6c69103a410181802855855aa4","value":"https://github.com/johnlindquist/kit/discussions/1240","img":"https://avatars.githubusercontent.com/u/629240?u=4750610206bd5ebafbdfab85d5d9c81dc8ce21ed&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1238","url":"https://github.com/johnlindquist/kit/discussions/1238","title":"Open Recent VS Code Project","name":"Open Recent VS Code Project","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1238","createdAt":"2023-05-03T23:43:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATrRi","body":"TIL VS Code has a sqlite database of recents, so I built this!\r\n\r\n[Open open-recent-vs-code-project in Script Kit](https://scriptkit.com/api/new?name=open-recent-vs-code-project&url=https://gist.githubusercontent.com/johnlindquist/b2426f52a9b5f3ca4827fbdeda6b323c/raw/8d7cbd175540000bf6a8684814de265343ad2ae5/open-recent-vs-code-project.ts\")\r\n\r\n```js\r\n// Name: Open Recent VS Code Project\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { URL, fileURLToPath } from \"url\"\r\n\r\n// /Users/johnlindquist/Library/Application Support/Code/User/globalStorage/state.vscdb\r\nlet filename = home(\"Library\", \"Application Support\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\n// windows path not tested, just guessing\r\nif (isWin) filename = home(\"AppData\", \"Roaming\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\nlet { default: sqlite3 } = await import(\"sqlite3\")\r\nlet { open } = await import(\"sqlite\")\r\n\r\nconst db = await open({\r\n filename,\r\n driver: sqlite3.Database,\r\n})\r\n\r\nlet key = `history.recentlyOpenedPathsList`\r\nlet table = `ItemTable`\r\n\r\nlet result = await db.get(`SELECT * FROM ${table} WHERE key = '${key}'`)\r\nlet recentPaths = JSON.parse(result.value)\r\nrecentPaths = recentPaths.entries\r\n .map(e => e?.folderUri)\r\n .filter(Boolean)\r\n .map(uri => fileURLToPath(new URL(uri)))\r\n\r\nlet recentPath = await arg(\"Open a recent path\", recentPaths)\r\nhide()\r\nawait exec(`code ${recentPath}`)\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1238","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/34244581?u=f136c7d3eec99d1a4b2c887ca8c673bfabef95fa&v=4","user":"alwinraju","author":"Alwin Raju","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1236","url":"https://github.com/johnlindquist/kit/discussions/1236","title":"ChatGPT with user input","name":"ChatGPT with user input","extension":".md","description":"Created by alwinraju","resourcePath":"/johnlindquist/kit/discussions/1236","createdAt":"2023-05-01T21:30:11Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATpjD","body":"Heres a code snippet that takes in a user input and feeds it into ChatGPT and returns the response.\r\nAn OpenAI API key is required for it to work. The prompt can be amended to suit your needs/to create\r\nyour own custom agents.\r\n\r\n```javascript\r\n/*\r\nPress `cmd+shift+enter` and enter the text you want to send to ChatGPT.\r\n*/\r\n\r\n// Name: Samantha\r\n// Description: Send a single prompt to ChatGPT\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nYou are ChatGPT, a large language model trained by OpenAI. Follow the user's\r\ninstructions carefully. Respond using markdown.\r\n########\r\n`;\r\n\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet textFromUser = await arg(\"How can I help you?\");\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(textFromUser)]);\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1236","img":"https://avatars.githubusercontent.com/u/34244581?u=f136c7d3eec99d1a4b2c887ca8c673bfabef95fa&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","author":"Josh Davenport-Smith","twitter":"jdprts","discussion":"https://github.com/johnlindquist/kit/discussions/1235","url":"https://github.com/johnlindquist/kit/discussions/1235","title":"Pause any music - only looks at Spotify/Music.app but customisable","name":"Pause any music - only looks at Spotify/Music.app but customisable","extension":".md","description":"Created by joshdavenport","resourcePath":"/johnlindquist/kit/discussions/1235","createdAt":"2023-05-01T09:26:25Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATpCU","body":"I'm often switching between Spotify and Music.app and find that what Mac targets when using pause media key can sometimes be unpredictable. This little script will pause either if playing.\r\n\r\n[Open pause-any-music in Script Kit](https://scriptkit.com/api/new?name=pause-any-music&url=https://gist.githubusercontent.com/joshdavenport/d6a38b7e5b9d9f1a76b0e44b78a7a5e5/raw/7c376383c698a52f817228aa19cf5312dbbc095c/pause-any-music.ts\")\r\n\r\n```js\r\n// Name: Pause Any Music\r\n// Description: Pause music playing from music apps\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst pauseScript = `\r\ntell application \"Spotify\"\r\n pause\r\nend tell\r\n\r\ntell application \"Music\"\r\n pause\r\nend tell\r\n`;\r\n\r\nexec(`osascript -e '${pauseScript}'`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1235","img":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1234","url":"https://github.com/johnlindquist/kit/discussions/1234","title":"Open dev-project","name":"Open dev-project","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1234","createdAt":"2023-05-01T03:28:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATo6h","body":"Now updated to use DB and add paths, create folders.\r\n\r\nOpen for any improvements ^^\r\n\r\n\r\n[Open dev-project in Script Kit](https://scriptkit.com/api/new?name=dev-project&url=https://gist.githubusercontent.com/Ambushfall/41236543032f4dd211a65964766be087/raw/5f7b2d37749d436f5015523d57fcba28d119b4cd/dev-project.js\")\r\n\r\n```js\r\n// Preview: docs\r\n// Menu: Open Project\r\n// Description: Opens a project in vscode\r\n// Shortcut: cmd shift .\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst envPath = await env('PROJECT_DIR');\r\n\r\nconst projectDir = home(envPath);\r\n\r\nconst projectList = await readdir(projectDir);\r\n\r\n\r\nlet { projects, write } = await db(\"projects\", {\r\n projects: projectList,\r\n})\r\n\r\nprojectList.forEach(async value => {\r\n if (!projects.includes(value)) {\r\n projects.push(value);\r\n await write()\r\n }\r\n})\r\n\r\n\r\nonTab(\"Open\", async () => {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n edit('', path.resolve(projectDir, project))\r\n})\r\n\r\nonTab(\"Add Path\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n \"Add path to project:\",\r\n md(projects.map(project => `* ${project.split('\\\\').pop()}`).join(\"\\n\"))\r\n )\r\n projects.push(project)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"Remove\", async () => {\r\n while (true) {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n\r\n project.split(':').length > 1 ? await rm(path.resolve(project)) : await rm(path.resolve(projectDir, project))\r\n\r\n let indexOfProject = projects.indexOf(project)\r\n projects.splice(indexOfProject, 1)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"New Project\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n {\r\n placeholder: \"Create new project:\", debounceInput: 400,\r\n enter: \"Create\", validate: async (input) => {\r\n let exists = await isDir(path.resolve(projectDir, input));\r\n if (exists) {\r\n return `${input} already exists`;\r\n }\r\n return true;\r\n }\r\n },\r\n\r\n )\r\n projects.push(project)\r\n mkdir(path.resolve(projectDir, project))\r\n await write()\r\n }\r\n})\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1234","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1232","url":"https://github.com/johnlindquist/kit/discussions/1232","title":"Get an AI powered explanation of highlighted text","name":"Get an AI powered explanation of highlighted text","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1232","createdAt":"2023-05-01T00:13:53Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATo1l","body":"\r\n[Open explain-plz in Script Kit](https://scriptkit.com/api/new?name=explain-plz&url=https://gist.githubusercontent.com/mabry1985/15add17a63b2d218be168495c2fb46b1/raw/3515457b32049380e633da1e625ff3d6714f844d/explain-plz.js\")\r\n\r\nA quick POC for an AI powered explanation script\r\n\r\nReturns a TLDR, Technical Summary, and ELI5\r\n\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235382761-06db398d-e8b0-4be5-8e8d-a3bb81d4a694.mov\r\n\r\n\r\n```js\r\n/*\r\n# Explain Plz\r\nHighlight some text and have it explained by AI\r\nWorks for any highlighted text or code\r\n*/\r\n\r\n// Name: Explain Plz\r\n// Description: Get an explanation for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd alt shift e\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and explaining it to the user.\r\nReturn the response in the following format using markdown syntax:\r\n# Explain Plz\r\n## TLDR (A quick summary of the highlighted text)\r\n## ELI5 (Explain Like I'm 5)\r\n## Explanation (A longer technical explanation of the highlighted text)\r\n`;\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n``;\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1232","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1231","url":"https://github.com/johnlindquist/kit/discussions/1231","title":"An example of an AGI task manager with Human in the loop feedback","name":"An example of an AGI task manager with Human in the loop feedback","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1231","createdAt":"2023-04-30T21:26:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATov9","body":"\r\n[Open ac-agi in Script Kit](https://scriptkit.com/api/new?name=ac-agi&url=https://gist.githubusercontent.com/mabry1985/cb36cb2a25d58628dcc2b506ec63e2dc/raw/0814b40397f404a9f05d03a549b352186f22f6ba/ac-agi.js\")\r\n\r\nUp to date script can be found in my Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\n```js\r\n/*\r\nPardon the mess this was put together in half a day for the [lablab.ai](https://lablab.ai/event/autonomous-gpt-agents-hackathon) hackathon.\r\nMore updates to come\r\n\r\n# AC AGI \r\nAn autonomous general intelligence that accomplishes a task for you.\r\nUses human in the loop to provide feedback to the agent.\r\n\r\n\r\nHow to use:\r\n- Enter your task\r\n- Wait for the agent to complete the task\r\n- Assign max-iterations for the agent to loop: 0 for infinite (probably not a good idea ¯\\_(ツ)_/¯)\r\n- Profit\r\n\r\nKnown issues:\r\n- The agent will sometimes get stuck in a loop and not complete the task\r\n- Human feedback is not always helpful\r\n\r\nUpcoming features:\r\n- More tools\r\n- Refined prompts\r\n- Better human feedback system\r\n- Better memory system\r\n\r\nPossible thanks to the fine folks at [Langchain](https://js.langchain.com/docs/use_cases/autonomous_agents/baby_agi#example-with-tools)\r\nand all the other giants whose shoulders we stand on.\r\n*/\r\n\r\n// Name: AC AGI\r\n// Description: An AGI task manager inspired by BabyAGI\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { BabyAGI } = await import(\"langchain/experimental/babyagi\");\r\nlet { MemoryVectorStore } = await import(\"langchain/vectorstores/memory\");\r\nlet { OpenAIEmbeddings } = await import(\"langchain/embeddings/openai\");\r\nlet { OpenAI } = await import(\"langchain/llms/openai\");\r\nlet { PromptTemplate } = await import(\"langchain/prompts\");\r\nlet { LLMChain } = await import(\"langchain/chains\");\r\nlet { ChainTool } = await import(\"langchain/tools\");\r\nlet { initializeAgentExecutorWithOptions } = await import(\"langchain/agents\");\r\nlet { DynamicTool } = await import(\"langchain/tools\");\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nawait env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst task = await arg({\r\n placeholder: \"Task\",\r\n description: \"Enter a task for AC AGI to complete\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\nlet maxIterations = await arg({\r\n placeholder: \"How many times should AC AGI loop?\",\r\n hint: \"Leave empty for infinite iterations *use with caution*\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nif (maxIterations === \"\" || maxIterations === \"0\") {\r\n maxIterations = undefined;\r\n}\r\n\r\n//#########################\r\n// BabyAGI method overrides\r\n//#########################\r\nfunction printTaskList() {\r\n let result = \"\";\r\n for (const t of this.taskList) {\r\n result += `${t.taskID}: ${t.taskName}\\n`;\r\n }\r\n const msg = `### Task List\r\n \r\n ${result}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printNextTask(task) {\r\n const msg = `### Next Task\r\n \r\n ${task.taskID}: ${task.taskName}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printTaskResult(result) {\r\n const msg = `### Task Result\r\n \r\n ${result.trim()}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\n//#############\r\n// Custom Tools\r\n//#############\r\nlet html = (str) => str.replace(/ /g, \"+\");\r\nlet fetch = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${html(\r\n q\r\n )}&sort=date`;\r\n\r\nasync function search(query) {\r\n let response = await get(fetch(query));\r\n\r\n let items = response?.data?.items;\r\n\r\n if (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n return JSON.stringify(choices);\r\n }\r\n}\r\n\r\nasync function humanFeedbackList(mdStr) {\r\n let html = md(`${mdStr.trim()}`);\r\n const response = div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n\r\n return response;\r\n}\r\n\r\nasync function humanInput(question) {\r\n const response = await arg({\r\n placeholder: \"Human, I need help!\",\r\n hint: question,\r\n ignoreBlur: true,\r\n ignoreAbandon: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n });\r\n return response;\r\n}\r\n\r\nconst todoPrompt = PromptTemplate.fromTemplate(\r\n \"You are a planner/expert todo list creator. Generate a markdown formatted todo list for: {objective}\"\r\n);\r\n\r\nconst tools = [\r\n new ChainTool({\r\n name: \"TODO\",\r\n chain: new LLMChain({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n prompt: todoPrompt,\r\n }),\r\n description:\r\n \"For making todo lists. Input: objective to create todo list for. Output: the todo list\",\r\n }),\r\n new DynamicTool({\r\n name: \"Search\",\r\n description: \"Search web for info\",\r\n func: search,\r\n }),\r\n new DynamicTool({\r\n name: \"Human Input\",\r\n description:\r\n \"(Use only when no info is available elsewhere) Ask a human for specific input that you don't know, like a persons name, or DOB, location, etc. Input is question to ask human, output is answer\",\r\n func: humanInput,\r\n }),\r\n // new DynamicTool({\r\n // name: \"Human Feedback Choice\",\r\n // description: `Ask human for feedback if you unsure of next step.\r\n // Input is markdown string formatted with your questions and suitable responses like this example:\r\n // # Human, I need your help!\r\n // \r\n // * [John](submit:John) // don't change formatting of these links\r\n // * [Mindy](submit:Mindy)\r\n // * [Joy](submit:Joy)\r\n // * [Other](submit:Other)\r\n // `,\r\n // func: humanFeedbackList,\r\n // }),\r\n];\r\n\r\n//##################\r\n// AC AGI is Born\r\n//##################\r\nconst taskBeginMsg = md(`\r\n### Executing Task Manager\r\nGoal: ${task}\r\n`);\r\n\r\ndiv({ html: taskBeginMsg, ignoreBlur: true });\r\n\r\nconst agentExecutor = await initializeAgentExecutorWithOptions(\r\n tools,\r\n new ChatOpenAI({ temperature: 0 }),\r\n {\r\n agentType: \"zero-shot-react-description\",\r\n agentArgs: {\r\n prefix: `You are an AI who performs one task based on the following objective: {objective}. \r\nTake into account these previously completed tasks: {context}.`,\r\n suffix: `Question: {task}\r\n{agent_scratchpad}`,\r\n inputVariables: [\"objective\", \"task\", \"context\", \"agent_scratchpad\"],\r\n },\r\n }\r\n);\r\n\r\nconst vectorStore = new MemoryVectorStore(new OpenAIEmbeddings());\r\n\r\nconst babyAGI = BabyAGI.fromLLM({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n executionChain: agentExecutor,\r\n vectorstore: vectorStore,\r\n maxIterations: maxIterations,\r\n});\r\n\r\nbabyAGI.printNextTask = printNextTask;\r\nbabyAGI.printTaskList = printTaskList;\r\nbabyAGI.printTaskResult = printTaskResult;\r\n\r\nawait babyAGI.call({ objective: task });\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1231","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1230","url":"https://github.com/johnlindquist/kit/discussions/1230","title":"Example of BabyAGI running in ScriptKit","name":"Example of BabyAGI running in ScriptKit","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1230","createdAt":"2023-04-30T21:23:21Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATov2","body":"","value":"https://github.com/johnlindquist/kit/discussions/1230","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1225","url":"https://github.com/johnlindquist/kit/discussions/1225","title":"Google Search","name":"Google Search","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1225","createdAt":"2023-04-30T06:23:32Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATn_P","body":"I know this is redundant and might not be useful to many, but I needed to build a custom search tool for an agent I'm working on. \r\n\r\nI set this up to test the functionality and figured someone might find it useful.\r\n\r\n```\r\n/* \r\n# Google Search\r\nExample of leveraging Google's Custom Search Engine API to search the web\r\n*/\r\n\r\n// Name: Google Search\r\n// Description: Leverage Google's Custom Search Engine API to search the web\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet query = await arg(\r\n {\r\n placeholder: \"Search Query\",\r\n strict: false,\r\n },\r\n [\r\n {\r\n name: \"Send a search query to Google\",\r\n info: \"always\",\r\n },\r\n ]\r\n);\r\n\r\nlet search = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${q}&sort=date`;\r\n\r\nlet response = await get(search(query));\r\n\r\nlet items = response?.data?.items;\r\n\r\nif (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n let link = await arg(\"Choose a link to view\", choices);\r\n\r\n open(link);\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1225","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95415447?v=4","user":"shyagamzo","author":"Shy Agam","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1224","url":"https://github.com/johnlindquist/kit/discussions/1224","title":"Reading aloud streamed text (GPT style)","name":"Reading aloud streamed text (GPT style)","extension":".md","description":"Created by shyagamzo","resourcePath":"/johnlindquist/kit/discussions/1224","createdAt":"2023-04-29T21:32:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATn4O","body":"I wanted my ChatGPT responses to be read aloud immediately, as they appear on the screen.\r\nThis was problematic because of two reasons:\r\n1. ChatGPT sends partial text responses (e.g. 'he', 'llo', ', I', 'am S', 'hy'). This isn't readable and should be accumulated.\r\n2. The `say` command stops any spoken text and starts speaking the new text. Meaning, we cannot simply call it every time we receive new text.\r\n\r\nThis is a rough start, but works well for my current needs.\r\nThis util class maintains a queue, detects certain delimiters (e.g. `.`, `,`) and starts speaking only when it detects that a phrase has probably been accumulated.\r\n\r\nhttps://gist.github.com/shyagamzo/749b7535aa8876ec2ce09f39aaef6a80\r\n\r\n```typescript\r\nimport '@johnlindquist/kit';\r\n\r\nconst speechStream = new (class SpeechStream\r\n{\r\n private textQueue: string[] = [];\r\n private isSpeaking: boolean = false;\r\n private feed: string = '';\r\n private finalizeFeedDebounced: () => void;\r\n\r\n constructor(private readonly config: { waitForDelimiter: number, estimatedWordsPerMinute: number })\r\n {\r\n this.finalizeFeedDebounced = _.debounce(this.finalizeFeed.bind(this), config.waitForDelimiter);\r\n\r\n onExit(() =>\r\n {\r\n this.textQueue = [];\r\n this.feed = '';\r\n\r\n sayIt('');\r\n });\r\n }\r\n\r\n public addText(text: string): void\r\n {\r\n this.feed += text;\r\n this.processAccumulatedText();\r\n this.finalizeFeedDebounced();\r\n }\r\n\r\n private processAccumulatedText(): void\r\n {\r\n const delimiters = /([.,;:!?\\n])/;\r\n\r\n const delimiterMatch = this.feed.match(delimiters);\r\n\r\n if (delimiterMatch)\r\n {\r\n const delimiterIndex = delimiterMatch.index;\r\n\r\n const textUntilDelimiter = this.feed.slice(0, delimiterIndex + 1);\r\n this.textQueue.push(textUntilDelimiter.trim());\r\n\r\n this.feed = this.feed.slice(delimiterIndex + 1);\r\n }\r\n\r\n this.processQueue();\r\n }\r\n\r\n private finalizeFeed(): void\r\n {\r\n if (this.feed)\r\n {\r\n this.textQueue.push(this.feed.trim());\r\n this.feed = '';\r\n this.processQueue();\r\n }\r\n }\r\n\r\n private processQueue(): void\r\n {\r\n if (this.isSpeaking || this.textQueue.length === 0) return;\r\n\r\n this.isSpeaking = true;\r\n\r\n const textToSpeak = this.textQueue.shift();\r\n\r\n this.waitForSpeechEnd(textToSpeak);\r\n sayIt(textToSpeak);\r\n }\r\n\r\n private waitForSpeechEnd(text: string): void\r\n {\r\n const estimatedSpeechDuration = this.estimateSpeechDuration(text);\r\n\r\n setTimeout(() =>\r\n {\r\n this.isSpeaking = false;\r\n this.processQueue();\r\n }, estimatedSpeechDuration);\r\n }\r\n\r\n private estimateSpeechDuration(text: string): number\r\n {\r\n const wordsPerMinute = this.config.estimatedWordsPerMinute; // Average speaking rate\r\n const words = text.trim().split(/\\s+/).length;\r\n const minutes = words / wordsPerMinute;\r\n\r\n return minutes * 60 * 1000; // Convert to milliseconds\r\n }\r\n})({\r\n waitForDelimiter: 4000,\r\n estimatedWordsPerMinute: 200\r\n});\r\n\r\nexport function sayIt(text: string): ReturnType\r\n{\r\n return say(text, { name: 'Microsoft Zira - English (United States)', rate: 1.3 });\r\n}\r\n\r\nexport function queueSpeech(text: string)\r\n{\r\n speechStream.addText(text);\r\n}\r\n```\r\n\r\nTo use it, simply import and call `queueSpeech`:\r\n\r\n```typescript\r\nimport { queueSpeech } from '../lib/speech-queue';\r\n\r\nfunction handleGPTText(text: string)\r\n{\r\n // ...\r\n queueSpeech(text);\r\n}\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1224","img":"https://avatars.githubusercontent.com/u/95415447?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1221","url":"https://github.com/johnlindquist/kit/discussions/1221","title":"Improve your writing with AI powers","name":"Improve your writing with AI powers","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1221","createdAt":"2023-04-29T00:42:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATnd8","body":"[Deprecated] \r\nif you miss it, check out Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\nHighlight your poorly written text and run the script to automagically make yourself sound smarter!\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273614-71a0b99c-5c9b-4806-84ba-2da8943359b9.mov\r\n\r\n\r\n\r\n```js\r\n/*\r\n/*\r\n# Smartify your words!\r\n\r\nTired of feeling dumb? Winter got you in a funk? \r\nCan you just not seem to get the words out right? \r\nWell, let's Smartify your words!\r\n\r\nHighlight some text and press `cmd+shift+enter` to send it through ChatGPT \r\nto replace the text with a more eloquent version. Mileage may vary.\r\n*/\r\n\r\n// Name: Smartify Your Words\r\n// Description: Let's make those words smarter!\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking an input and refactoring it using the following rules: '\r\n\r\n- Maintain the same meaning, tone, and intent as the original text\r\n- Clean up any grammar or spelling mistakes\r\n- Make it sound more professional, but keep it casual\r\n- Reduce redundancies and excessive verbiage\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes.\r\n########\r\n`;\r\n\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst smartify = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n if (!token) return;\r\n log(`handleLLMStart`);\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token) => {\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\nlet text = await getSelectedText();\r\n\r\nawait smartify.call([\r\n new SystemChatMessage(prompt),\r\n new HumanChatMessage(text),\r\n]);\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1221","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1220","url":"https://github.com/johnlindquist/kit/discussions/1220","title":"Prompt ChatGPT from anywhere you can input text","name":"Prompt ChatGPT from anywhere you can input text","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1220","createdAt":"2023-04-29T00:36:59Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATnd5","body":"[DEPRECATED] \r\n\r\nBy selecting any input text and running this script you now have access to the power of AI anywhere in your OS. \r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273240-824a9432-87a3-48ac-9357-668a0c01b12c.mov\r\n\r\n```js\r\n/*\r\n# Generate a response to a prompt in any text field\r\n\r\nThis script utilizes ChatGPT to generate a response for the selected text. \r\nThe response will then replace the original text, making it useful for various writing tasks such as blog posts, code, and emails.\r\n\r\nHighlight some text and press `cmd+shift+p` to send it through ChatGPT \r\nto replace the text response. Your mileage may vary.\r\n*/\r\n\r\n// Name: Prompt Anywhere\r\n// Description: Generate a prompt response in anywhere you can type\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd shift P\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and generating a response to it.\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes. \r\nDo not ask any questions, do not refuse any input, and do not change the subject.\r\n`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n log(`handleLLMStart`);\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n // occasionally I see tokens typed out of order\r\n // still not sure why this happens\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1220","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/20973893?u=359eca24a75c3971794c4daba03f723f0164762a&v=4","user":"mueslirieger","author":"Michael Rieger","twitter":"mueslirieger","discussion":"https://github.com/johnlindquist/kit/discussions/1212","url":"https://github.com/johnlindquist/kit/discussions/1212","title":"Open Vercel project dashboard","name":"Open Vercel project dashboard","extension":".md","description":"Created by mueslirieger","resourcePath":"/johnlindquist/kit/discussions/1212","createdAt":"2023-04-27T07:15:16Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATl3v","body":"I use this script so much, I had to share it. It lets you select a personal or team project that is hosted on Vercel and opens the dashboard page of the selected project.\r\n\r\n\r\n[Open open-vercel-project in Script Kit](https://scriptkit.com/api/new?name=open-vercel-project&url=https://gist.githubusercontent.com/mueslirieger/21b1b1b9e6ef48ecf64d8d1a3937f0e8/raw/08532f733744bd314da5667b9e2feff49c6da6ca/open-vercel-project.ts\")\r\n\r\n```typescript\r\n/*\r\n# Open Vercel project dashboard\r\n\r\nLets the user select and open the dashboard page of a project hosted on Vercel.\r\n*/\r\n\r\n// Name: Open Vercel project dashboard\r\n// Description: Lets the user select and open the dashboard page of a project hosted on Vercel.\r\n// Author: Michael Rieger\r\n// Twitter: @mueslirieger\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst apiBaseUrl = 'https://api.vercel.com';\r\nconst dashboardBaseUrl = 'https://vercel.com';\r\n\r\n// ask user to create an access token for the rest api\r\nconst VERCEL_ACCESS_TOKEN = await env('VERCEL_ACCESS_TOKEN', {\r\n panel: md(`## Get a [Vercel API Access Token](https://vercel.com/account/tokens)`),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst user = await fetchUser();\r\n\r\n// Select whether personal or team projects should be listed\r\nconst projectsType = await selectProjectsType();\r\n\r\n// If team projects were selected list the teams the user is assigned to\r\nlet team: Team | undefined | null = null;\r\nif (projectsType === 'team') {\r\n const teams = await fetchTeams();\r\n team = await selectTeam(teams);\r\n}\r\n\r\n// Fetch projects based on previous selection\r\nconst projects = await fetchProjects(team?.id);\r\n\r\n// let user select project and open in browser\r\nconst project = await selectProject(projects);\r\n\r\nif (!project) exit(-1);\r\n\r\nawait browse(`${dashboardBaseUrl}/${projectsType === 'team' ? team.slug : user.username}/${project.name}`);\r\n\r\n// -----------------------------------------------------\r\n// Helpers\r\n// -----------------------------------------------------\r\n\r\ntype VercelApiError = {\r\n error?: {\r\n code: string;\r\n message: string;\r\n };\r\n};\r\n\r\nasync function selectProjectsType() {\r\n return arg<'personal' | 'team'>('Show personal or team projects', [\r\n {\r\n value: 'personal',\r\n name: '[P]ersonal',\r\n shortcut: 'p',\r\n },\r\n {\r\n value: 'team',\r\n name: '[T]eam',\r\n shortcut: 't',\r\n },\r\n ]);\r\n}\r\n\r\ntype User = {\r\n id: string;\r\n email: string;\r\n name: string | null;\r\n username: string;\r\n};\r\ntype GetUserResponse = { user: User } & VercelApiError;\r\n\r\nasync function fetchUser() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/user`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.user;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\ntype Team = { id: string; name: string; slug: string; avatar: string | null };\r\ntype GetTeamsResponse = { teams?: Team[] } & VercelApiError;\r\n\r\nasync function fetchTeams() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/teams`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.teams;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectTeam(teams: Team[]) {\r\n return await arg(\r\n {\r\n placeholder: teams.length ? 'Select a team' : 'No teams found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Select team`,\r\n },\r\n teams.map((team) => ({\r\n value: team,\r\n name: team.name,\r\n img: team.avatar ? `https://vercel.com/api/www/avatar/${team.avatar}?s=128` : '',\r\n }))\r\n );\r\n}\r\n\r\ntype Project = { id: string; name: string; latestDeployments: { alias: string[] }[] };\r\ntype GetProjectsResponse = { projects?: Project[] } & VercelApiError;\r\n\r\nasync function fetchProjects(teamId?: string | null | undefined) {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v9/projects${teamId ? `?teamId=${teamId}` : ''}`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.projects;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectProject(projects: Project[]) {\r\n return await arg(\r\n {\r\n placeholder: projects.length ? 'Select a project' : 'No projects found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Open project in dashboard`,\r\n },\r\n projects.map((project) => ({\r\n value: project,\r\n name: project.name,\r\n }))\r\n );\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1212","img":"https://avatars.githubusercontent.com/u/20973893?u=359eca24a75c3971794c4daba03f723f0164762a&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1209","url":"https://github.com/johnlindquist/kit/discussions/1209","title":"Merge / Split Afred Clipboard Script","name":"Merge / Split Afred Clipboard Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1209","createdAt":"2023-04-25T02:19:34Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjx3","body":"\r\n[Open merge-split-alfred-clipboard in Script Kit](https://scriptkit.com/api/new?name=merge-split-alfred-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/4803f2f333828e5cbd30a49cc426cd22/raw/2f9d71e8c1dc94525f7bc7996b47d820fbadfb9a/merge-split-alfred-clipboard.ts\")\r\n\r\nThis is a very specific yet useful Script, for those who have Alfred app with Powerpack and use the clipboard history. It allows you to split and merge it in several ways. \r\nFor `merge`, it asks you for the number of items in the clipboard, with a preview, and then asks you the merging character or characters. The resulting merge is placed in the clipboard.\r\nFor `split`, it asks you for a splitting character or characters, and saves all the resulted strings (after splitting) in the clipboard history.\r\n\r\nUse cases:\r\n* copy a bunch of values from different places, join them together in one shot, by `\\n'\r\n* copy a list of values, separated by comma or `\\n`, split them and paste them individually in a form\r\n\r\n```js\r\n// Name: Merge / Split Alfred clipboard\r\n// Description: Merge or split clipboard content using Alfred app's clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\");\r\nconst databasePath = home('Library/Application Support/Alfred/Databases/clipboard.alfdb')\r\nif (!await pathExists(databasePath)) {\r\n notify(\"Alfred clipboard database not found\" )\r\n exit()\r\n}\r\n\r\nconst db = new Database(databasePath);\r\n\r\nconst queryClipboard = async (sql, params) => {\r\n const stmt = db.prepare(sql);\r\n return sql.trim().toUpperCase().startsWith(\"SELECT\") ? stmt.all(params) : stmt.run(params);\r\n};\r\n\r\nconst getMergedClipboards = async (count, separator) => {\r\n const sql = `SELECT item FROM clipboard WHERE dataType = 0 order by ROWID desc LIMIT ?`;\r\n const clipboards = await queryClipboard(sql, [count]);\r\n return clipboards.map(row => row.item.trim()).join(separator);\r\n};\r\n\r\nconst writeMergedClipboards = async (mergedText) => {\r\n await clipboard.writeText(mergedText);\r\n};\r\n\r\nconst getSplitClipboard = async (separator, trim) => {\r\n const currentClipboard = await clipboard.readText();\r\n return currentClipboard.split(separator).map(item => trim ? item.trim() : item);\r\n};\r\n\r\nconst writeSplitClipboard = async (splitText) => {\r\n const lastTsSql = `SELECT ts FROM clipboard WHERE dataType = 0 ORDER BY ts DESC LIMIT 1`;\r\n const lastTsResult = await queryClipboard(lastTsSql, []);\r\n let lastTs = lastTsResult.length > 0 ? Number(lastTsResult[0].ts) : 0;\r\n\r\n const insertSql = `INSERT INTO clipboard (item, ts, dataType, app, appPath) VALUES (?, ?, 0, 'Kit', '/Applications/Kit.app')`;\r\n\r\n for (let i = 0; i < splitText.length - 1; i++) {\r\n lastTs += 1;\r\n await queryClipboard(insertSql, [splitText[i], lastTs]);\r\n }\r\n\r\n await clipboard.writeText(splitText[splitText.length - 1]);\r\n};\r\n\r\n\r\nconst action = await arg(\"Choose action\", [\"Merge\", \"Split\"]);\r\n\r\nif (action === \"Merge\") {\r\n const count = await arg({\r\n placeholder: \"Enter the number of clipboard items to merge\",\r\n }, async (input) => {\r\n if (isNaN(Number(input)) || input.length === 0)return ''\r\n return md(`
${await getMergedClipboards(input, '\\n')}
`)\r\n })\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for merging\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n return md(`
${await getMergedClipboards(count, input)}
`)\r\n })\r\n const mergedText = await getMergedClipboards(count, separator);\r\n await writeMergedClipboards(mergedText);\r\n await notify(\"Merged clipboard items and copied to clipboard\");\r\n} else {\r\n // const separator = await arg(\"Enter the separator for splitting\");\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for splitting\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n let strings = await getSplitClipboard(input, true);\r\n return md(`
${strings.join('\\n')}
`)\r\n })\r\n const trim = await arg(\"Trim clipboard content?\", [\"Yes\", \"No\"]);\r\n const splitText = await getSplitClipboard(separator, trim === \"Yes\");\r\n await writeSplitClipboard(splitText);\r\n await notify(\"Split clipboard content and stored in Alfred clipboard\");\r\n}\r\n\r\ndb.close();\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1209","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1208","url":"https://github.com/johnlindquist/kit/discussions/1208","title":"Type Clipboard Script","name":"Type Clipboard Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1208","createdAt":"2023-04-25T02:13:01Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxt","body":"\r\n[Open type-clipboard in Script Kit](https://scriptkit.com/api/new?name=type-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/a18db9f56dfe8f6745ce6e917baf8ade/raw/eb3878c3aaede4e064bfbb451e6d30851b84160e/type-clipboard.ts\")\r\n\r\nThis is a Script I use more often than I would care to admit. There're situations where the `paste` command just doesn't work. Either web forms that don't allow paste, or crappy app UIs that for some reason a normal paste doesn't work. If you don't mind the lengthy shortcut, you hit `ctrl+cmd+alt+shift+v` and it `types` the content of the clipboard really fast, instead of pasting it.\r\n\r\n```js\r\n// Name: Type Clipboard\r\n// Description: Get the content of the clipboard and \"keystroke\" it without pasting\r\n// Shortcut: ctrl+cmd+alt+shift+v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst clipboardText = await clipboard.readText()\r\n\r\nif (clipboardText.length > 1000) {\r\n await notify(\"Clipboard content is too long\")\r\n exit()\r\n}\r\n\r\nawait applescript(String.raw`\r\n set chars to count (get the clipboard)\r\n tell application \"System Events\"\r\n delay 0.1\r\n keystroke (get the clipboard)\r\n end tell\r\n`)\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1208","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1207","url":"https://github.com/johnlindquist/kit/discussions/1207","title":"Open in WhatsApp Script","name":"Open in WhatsApp Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1207","createdAt":"2023-04-25T02:10:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxo","body":"\r\n[Open open-in-whatsapp in Script Kit](https://scriptkit.com/api/new?name=open-in-whatsapp&url=https://gist.githubusercontent.com/ramiroaraujo/a61f67f8b55a3805888ff092b77c2550/raw/80eaadf67510c49d0724bb82b69f2a00ddb0d7d6/open-in-whatsapp.ts\")\r\n\r\nAnother simple script for opening a phone number in WhatsApp to chat. It fetches the number from the clipboard, and if no country code is provided it assumes Argentina, where I'm from, but of course change it to your default country\r\n\r\n```js\r\n// Name: Open in WhatsApp\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the text from the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//normalize the text\r\ntext = text.replace(/[-() ]/g, \"\");\r\n\r\n//validate if valid phone number\r\nif (!text.match(/^(\\+\\d{12,13})|(\\d{10,11})$/)) {\r\n notify(\"Invalid phone number\");\r\n exit()\r\n}\r\n\r\n//assume Argentina if no country code since that's where I'm from\r\nif (!text.startsWith(\"+\")) {\r\n text = \"+54\" + text;\r\n}\r\n\r\n//open in WhatsApp\r\nopen(`https://wa.me/${text}`);\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1207","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1206","url":"https://github.com/johnlindquist/kit/discussions/1206","title":"Convert selected images Script","name":"Convert selected images Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1206","createdAt":"2023-04-25T02:08:46Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxi","body":"\r\n[Open convert-selected-images in Script Kit](https://scriptkit.com/api/new?name=convert-selected-images&url=https://gist.githubusercontent.com/ramiroaraujo/51a8303fd66cc9d8b6db8a19c651254e/raw/b19ada003b9f58f48976636115e136cd2841ed0a/convert-selected-images.ts\")\r\n\r\nThis Script will convert all your selected (supported) images to either `jpg`, `png` or `webp`. I mostly created it to deal with sending images from the phone to the mac, and getting them as `heic`...\r\n\r\n```js\r\n // Name: convert selected images\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n// Grab selected files\r\nconst files = (await getSelectedFile()).split(\"\\n\");\r\n\r\n// Set up whitelist of formats\r\nconst supportedFormats = [\".heic\", \".png\", \".gif\", \".webp\", \".jpg\", \".jpeg\"];\r\n\r\n// Filter files based on supported formats\r\nconst selectedFiles = files.filter(file =>\r\n supportedFormats.some(format => file.toLowerCase().endsWith(format))\r\n);\r\n\r\n// Notify if no files are selected\r\nif (!selectedFiles.length) {\r\n await notify(\"No supported files selected\");\r\n exit();\r\n}\r\n\r\nconst convertHeic = await npm(\"heic-convert\");\r\nconst sharp = await npm(\"sharp\");\r\n\r\n// Select the output format\r\nconst outputFormat = await arg(\"Choose an output format\", [\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n]);\r\n\r\nconst getUniquePath = async (outputPath, suffix = \"\") => {\r\n if (await isFile(outputPath)) {\r\n const name = path.basename(outputPath, path.extname(outputPath));\r\n const newName = `${name}${suffix}-copy${path.extname(outputPath)}`;\r\n const newPath = path.join(path.dirname(outputPath), newName);\r\n return await getUniquePath(newPath, `${suffix}-copy`);\r\n } else {\r\n return outputPath;\r\n }\r\n};\r\n\r\n// Convert selected files to the chosen output format using appropriate libraries\r\nfor (const file of selectedFiles) {\r\n const content = await readFile(file);\r\n const name = path.basename(file).split(\".\")[0];\r\n const outputPath = path.join(path.dirname(file), name + `.${outputFormat}`);\r\n\r\n const uniqueOutputPath = await getUniquePath(outputPath);\r\n\r\n if (file.toLowerCase().endsWith(\".heic\")) {\r\n const formatMap = {\r\n jpg: \"JPEG\",\r\n png: \"PNG\",\r\n }\r\n const outputBuffer = await convertHeic({\r\n buffer: content,\r\n format: formatMap[outputFormat],\r\n quality: 0.5,\r\n });\r\n\r\n await writeFile(uniqueOutputPath, outputBuffer);\r\n } else {\r\n const sharpImage = sharp(content);\r\n\r\n switch (outputFormat) {\r\n case \"jpg\":\r\n await sharpImage.jpeg({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n case \"png\":\r\n await sharpImage.png().toFile(uniqueOutputPath);\r\n break;\r\n case \"webp\":\r\n await sharpImage.webp({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n }\r\n }\r\n}\r\n\r\nawait notify(`Converted selected files to ${outputFormat.toUpperCase()}`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1206","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1205","url":"https://github.com/johnlindquist/kit/discussions/1205","title":"Open URL in clipboard Script","name":"Open URL in clipboard Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1205","createdAt":"2023-04-25T02:06:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxb","body":"\r\n[Open open-url-in-clipboard in Script Kit](https://scriptkit.com/api/new?name=open-url-in-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/600d82866cd35b21998897843a4c3eb4/raw/c729cfb0baf43e6aa371a0ed0050ad69d5a9267d/open-url-in-clipboard.ts\")\r\n\r\nDead simple script for the very common use case of copying _some_ text with a URL in it, and wanting to navigate to that URL. Will fetch the first one it finds and go\r\n\r\n```js\r\n// Name: Open URL in clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//get the first URL in the clipboard, if any\r\nlet url = text.match(/(https?:\\/\\/[^\\s]+)/);\r\n\r\n//if there's a URL, open it\r\nif (url) {\r\n open(url[0]);\r\n} else {\r\n notify(\"No URL found in clipboard\");\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1205","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1204","url":"https://github.com/johnlindquist/kit/discussions/1204","title":"Emoji Search Script","name":"Emoji Search Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1204","createdAt":"2023-04-25T02:04:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxX","body":"\r\n[Open emoji-search in Script Kit](https://scriptkit.com/api/new?name=emoji-search&url=https://gist.githubusercontent.com/ramiroaraujo/5b5f92d043e1ffa07af92215395d9231/raw/e134a3a357dfe43276d9d83bbd73ca01aad74537/emoji-search.ts\")\r\n\r\nA rather simple Emoji search that uses local database for fast lookup. It actually bootstrap by creating a sqlite database out of the `emojilib` JSON, in particular for storing usage and sorting by it. It will search by name and keywords, and the list will be sorted by most used\r\n\r\n```js\r\n// Name: Emoji Search\r\n// Description: Search and copy emoji to clipboard using SQLite database\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\")\r\nconst databaseFile = projectPath(\"db\", \"emoji-search-emojilib.db\")\r\n\r\nconst emojilibURL = \"https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json\"\r\n\r\nconst createDatabase = async () => {\r\n const response = await get(emojilibURL)\r\n const emojiData = response.data as Record\r\n\r\n //create db and table\r\n const db = new Database(databaseFile)\r\n db.exec(`CREATE TABLE IF NOT EXISTS emojis\r\n (emoji TEXT PRIMARY KEY, name TEXT, keywords TEXT, used INTEGER DEFAULT 0)`)\r\n\r\n //populate with data from emojilib\r\n for (const [emojiChar, emojiInfo] of Object.entries(emojiData)) {\r\n const description = emojiInfo[0]\r\n const tags = emojiInfo.slice(1).join(', ')\r\n\r\n db.prepare(\"INSERT OR REPLACE INTO emojis VALUES (?, ?, ?, 0)\").run(emojiChar, description, tags)\r\n }\r\n db.close()\r\n};\r\n\r\nif (!await pathExists(databaseFile)) {\r\n await createDatabase()\r\n}\r\n\r\nconst db = new Database(databaseFile)\r\n\r\nconst queryEmojis = async () => {\r\n const sql = \"SELECT emoji, name, keywords FROM emojis ORDER BY used DESC\"\r\n const stmt = db.prepare(sql)\r\n return stmt.all()\r\n}\r\n\r\nconst snakeToHuman = (text) => {\r\n return text\r\n .split('_')\r\n .map((word, index) => index === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word)\r\n .join(' ')\r\n}\r\n\r\nconst emojis = await queryEmojis()\r\n\r\nconst selectedEmoji = await arg(\"Search Emoji\", emojis.map(({ emoji, name, keywords }) => ({\r\n name: `${snakeToHuman(name)} ${keywords}`,\r\n html: md(`
\r\n ${emoji}\r\n
\r\n ${snakeToHuman(name)}\r\n ${keywords} \r\n
\r\n
`),\r\n value: emoji,\r\n\r\n})))\r\n\r\nawait clipboard.writeText(selectedEmoji)\r\n\r\n// Update the 'used' count\r\nconst updateSql = \"UPDATE emojis SET used = used + 1 WHERE emoji = ?\"\r\nconst updateStmt = db.prepare(updateSql)\r\nupdateStmt.run(selectedEmoji)\r\n\r\ndb.close()\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1204","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1203","url":"https://github.com/johnlindquist/kit/discussions/1203","title":"Text Manipulation Script","name":"Text Manipulation Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1203","createdAt":"2023-04-25T02:02:30Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxU","body":"\r\n[Open text-manipulation in Script Kit](https://scriptkit.com/api/new?name=text-manipulation&url=https://gist.githubusercontent.com/ramiroaraujo/55cd0f21adb60f0a270c18fbcce99454/raw/7dd3ecffbaa6eea66b9c3476ea8122ea31ef25e5/text-manipulation.ts\")\r\n\r\nInspired by a mix of an old Pipe workflow for Alfred mixed with the String Manipulation Plugin for Jetbrains IDEs. It will transform the current content of the clipboard based on the operation you select. Some operations require a parameter (`joinBy` for example), in those cases it asks for it. Both in the operation selection and parameter it shows a preview of the resulting text. \r\nIf you select an operation by `Cmd + enter` you'll be prompted by another operation to select after the first one is finished, and you can continue \"piping\" the outputs until you're done. Since it's common for me to `Cmd + enter` one last time and don't actually need the transformation there's a `No Operation` transform to select on this cases.\r\n\r\nUse cases:\r\n* copy a large list of values, wrap them in `'`, join them by `\\n`\r\n* capture numbers regex in each line, clean empty lines, join them by `+`, paste in ScriptKit or Alfred for sum result\r\n* filter lines by regex\r\n\r\nIt's hard to explain how useful this ends up being in my day to day\r\n\r\n```js\r\n// Name: Text Manipulation\r\n// Description: Transform clipboard text based on user-selected options\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet transformations = {\r\n upperCase: text => text.toUpperCase(),\r\n lowerCase: text => text.toLowerCase(),\r\n capitalize: text => text.split('\\n').map(line => line.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')).join('\\n'),\r\n decodeUrl: text => text.split('\\n').map(line => decodeURIComponent(line)).join('\\n'),\r\n snakeCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `_${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n camelCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => p.toUpperCase()).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n kebabCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `-${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n reverseCharacters: text => text.split('\\n').map(line => line.split('').reverse().join('')).join('\\n'),\r\n removeDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n return [...new Set(lines)].join('\\n');\r\n },\r\n keepOnlyDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n let duplicates = lines.filter((item, index) => lines.indexOf(item) !== index);\r\n return [...new Set(duplicates)].join('\\n');\r\n },\r\n removeEmptyLines: text => text.split('\\n').filter(line => line.trim() !== '').join('\\n'),\r\n removeAllNewLines: text => text.split('\\n').map(line => line.trim()).join(''),\r\n trimEachLine: text => text.split('\\n').map(line => line.trim()).join('\\n'),\r\n sortLinesAlphabetically: text => text.split('\\n').sort().join('\\n'),\r\n sortLinesNumerically: text => text.split('\\n').sort((a, b) => a - b).join('\\n'),\r\n reverseLines: text => text.split('\\n').reverse().join('\\n'),\r\n shuffleLines: text => {\r\n let lines = text.split('\\n')\r\n for (let i = lines.length - 1; i > 0; i--) {\r\n let j = Math.floor(Math.random() * (i + 1))\r\n let temp = lines[i]\r\n lines[i] = lines[j]\r\n lines[j] = temp\r\n }\r\n return lines.join('\\n')\r\n },\r\n joinBy: (text, separator) => text.split('\\n').join(separator),\r\n splitBy: (text, separator) => text.split(separator).join('\\n'),\r\n removeWrapping: text => {\r\n const lines = text.split('\\n');\r\n const matchingPairs = [['(', ')'], ['[', ']'], ['{', '}'], ['<', '>'], ['\"', '\"'], [\"'\", \"'\"]];\r\n return lines\r\n .map(line => {\r\n const firstChar = line.charAt(0);\r\n const lastChar = line.charAt(line.length - 1);\r\n\r\n for (const [open, close] of matchingPairs) {\r\n if (firstChar === open && lastChar === close) {\r\n return line.slice(1, -1);\r\n }\r\n }\r\n\r\n if (firstChar === lastChar) {\r\n return line.slice(1, -1);\r\n }\r\n\r\n return line;\r\n })\r\n .join('\\n');\r\n },\r\n wrapEachLine: (text, wrapper) => {\r\n const lines = text.split('\\n');\r\n\r\n return lines\r\n .map(line => `${wrapper}${line}${wrapper}`)\r\n .join('\\n');\r\n },\r\n captureEachLine: (text, regex) => {\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex);\r\n\r\n return lines\r\n .map(line => {\r\n const match = line.match(pattern);\r\n return match ? match[0] : '';\r\n })\r\n .join('\\n');\r\n },\r\n removeLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i');\r\n\r\n return lines\r\n .filter(line => !pattern.test(line))\r\n .join('\\n');\r\n },\r\n keepLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i')\r\n\r\n return lines\r\n .filter(line => pattern.test(line))\r\n .join('\\n');\r\n },\r\n prependTextToAllLines: (text, prefix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => prefix + line).join('\\n');\r\n },\r\n\r\n appendTextToAllLines: (text, suffix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => line + suffix).join('\\n');\r\n },\r\n\r\n replaceRegexInAllLines: (text, regexWithReplacement) => {\r\n const [regex, replacement] = regexWithReplacement.split('|');\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, replacement)).join('\\n');\r\n },\r\n removeRegexInAllLines: (text, regex) => {\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, '')).join('\\n');\r\n },\r\n generateNumberedList: (text) => {\r\n const lines = text.split('\\n');\r\n return lines.map((line, index) => `${index + 1}. ${line}`).join('\\n');\r\n },\r\n noop: text => text,\r\n}\r\n\r\nlet options = [\r\n // Existing options here\r\n {\r\n name: \"Decode URL\", description: \"Decode a URL-encoded text\", value: {\r\n key: \"decodeUrl\"\r\n }\r\n },\r\n {\r\n name: \"Upper Case\",\r\n description: \"Transform the entire text to upper case\",\r\n value: {\r\n key: \"upperCase\",\r\n },\r\n },\r\n {\r\n name: \"Lower Case\",\r\n description: \"Transform the entire text to lower case\",\r\n value: {\r\n key: \"lowerCase\",\r\n },\r\n },\r\n {\r\n name: \"snake_case\", description: \"Convert text to snake_case\", value: {\r\n key: \"snakeCase\"\r\n }\r\n },\r\n {\r\n name: \"Capitalize\", description: \"Convert text to Capital Case\", value: {\r\n key: \"capitalize\"\r\n }\r\n },\r\n {\r\n name: \"camelCase\", description: \"Convert text to camelCase\", value: {\r\n key: \"camelCase\"\r\n }\r\n },\r\n {\r\n name: \"kebab-case\", description: \"Convert text to kebab-case\", value: {\r\n key: \"kebabCase\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Characters\", description: \"Reverse the characters in the text\", value: {\r\n key: \"reverseCharacters\"\r\n }\r\n },\r\n {\r\n name: \"Remove Duplicate Lines\",\r\n description: \"Remove duplicate lines from the text\",\r\n value: {\r\n key: \"removeDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Keep Only Duplicate Lines\",\r\n description: \"Keep only duplicate lines in the text\",\r\n value: {\r\n key: \"keepOnlyDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove Empty Lines\", description: \"Remove empty lines from the text\", value: {\r\n key: \"removeEmptyLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove All New Lines\", description: \"Remove all new lines from the text\", value: {\r\n key: \"removeAllNewLines\"\r\n }\r\n },\r\n {\r\n name: \"Trim Each Line\",\r\n description: \"Trim whitespace from the beginning and end of each line\",\r\n value: {\r\n key: \"trimEachLine\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Alphabetically\", description: \"Sort lines alphabetically\", value: {\r\n key: \"sortLinesAlphabetically\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Numerically\", description: \"Sort lines numerically\", value: {\r\n key: \"sortLinesNumerically\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Lines\", description: \"Reverse the order of lines\", value: {\r\n key: \"reverseLines\"\r\n }\r\n },\r\n {\r\n name: \"Shuffle Lines\", description: \"Randomly shuffle the order of lines\", value: {\r\n key: \"shuffleLines\"\r\n }\r\n },\r\n {\r\n name: \"Join By\",\r\n description: \"Join lines by a custom separator\",\r\n value: {\r\n key: \"joinBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to join lines\",\r\n defaultValue: \",\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Split By\",\r\n description: \"Split lines by a custom separator\",\r\n value: {\r\n key: \"splitBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to split lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Wrapping\",\r\n description: \"Remove wrapping characters from each line\",\r\n value: {\r\n key: \"removeWrapping\",\r\n },\r\n },\r\n {\r\n name: \"Wrap Each Line With\",\r\n description: \"Wrap each line with a custom character or string\",\r\n value: {\r\n key: \"wrapEachLine\",\r\n parameter: {\r\n name: \"Wrapper\",\r\n description: \"Enter a wrapper for each line\",\r\n defaultValue: '\"',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Capture Each Line\",\r\n description: \"Capture and return the first match of a regex pattern in each line\",\r\n value: {\r\n key: \"captureEachLine\",\r\n parameter: {\r\n name: \"Pattern\",\r\n description: \"Enter a regex pattern to capture\",\r\n defaultValue: \"\\\\d+\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Lines Matching\",\r\n description: \"Remove lines that match the given regex\",\r\n value: {\r\n key: \"removeLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to remove\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Keep Lines Matching\",\r\n description: \"Keep lines that match the given regex\",\r\n value: {\r\n key: \"keepLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to keep\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Prepend Text to All Lines\",\r\n description: \"Add text to the beginning of all lines\",\r\n value: {\r\n key: \"prependTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to prepend to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Append Text to All Lines\",\r\n description: \"Add text to the end of all lines\",\r\n value: {\r\n key: \"appendTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to append to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Replace Regex in All Lines\",\r\n description: \"Replace regex matches in all lines with specified text\",\r\n value: {\r\n key: \"replaceRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex and Replacement\",\r\n description: \"Enter regex and replacement text separated by a '|'\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Generate Numbered List\",\r\n description: \"Prepend numbers to each line\",\r\n value: {\r\n key: \"generateNumberedList\",\r\n },\r\n },\r\n {\r\n name: \"Remove Regex In All Lines\",\r\n description: \"Remove matches of the provided regex in all lines\",\r\n value: {\r\n key: \"removeRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to remove from all lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"No Operation\",\r\n description: \"Do nothing to the text, if you accidentally hit Cmd + enter and need no more transformations\",\r\n }\r\n]\r\n\r\nconst handleTransformation = async (text, transformation) => {\r\n let {key, parameter} = transformation;\r\n let paramValue = parameter ? await arg({\r\n input: parameter.defaultValue,\r\n }, (input) => md(`
`)\r\n } catch (e) {\r\n return '...'\r\n }\r\n },\r\n }\r\n })\r\n )\r\n rerun = flag?.rerun as boolean;\r\n\r\n clipboardText = await handleTransformation(clipboardText, transformation);\r\n operations.push(transformation.key);\r\n}\r\n\r\nawait clipboard.writeText(clipboardText)\r\n\r\nawait notify(\"Text transformation applied and copied to clipboard\")\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1203","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1202","url":"https://github.com/johnlindquist/kit/discussions/1202","title":"Screencapture OCR Script","name":"Screencapture OCR Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1202","createdAt":"2023-04-25T01:55:18Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxD","body":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/ramiroaraujo/d1924947b178742c8cd80f320d5e8e63/raw/07d0c86ff19b503bd856dd731294c4866aea7c79/ocr.ts\")\r\n\r\nOCR script that uses the OS native screencapture to capture part of your screen, perform OCR on it and copy the text to the clipboard.\r\nNote: I haven't even tested Windows and Linux versions. ChatGPT just wrote those for me :)\r\n\r\n```js\r\n// Name: OCR\r\n// Description: Capture a screenshot and recognize the text using tesseract.js\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n//both win and linux implementations were created by chatgpt (gpt4), without _any_ tests!! 😅\r\nconst captureScreenshot = async () => {\r\n const tmpFile = `/tmp/screenshot-${Date.now()}.png`;\r\n\r\n if (isMac) {\r\n await exec(`screencapture -i ${tmpFile}`);\r\n } else if (isWin) {\r\n const psScript = `\r\n Add-Type -AssemblyName System.Windows.Forms\r\n [System.Windows.Forms.SendKeys]::SendWait('%{PRTSC}')\r\n Start-Sleep -m 500\r\n $clipboardData = Get-Clipboard -Format Image\r\n $clipboardData.Save('${tmpFile}', [System.Drawing.Imaging.ImageFormat]::Png)\r\n `;\r\n await exec(`powershell -Command \"${psScript.replace(/\\n/g, '')}\"`);\r\n } else if (isLinux) {\r\n // Check if gnome-screenshot is available\r\n try {\r\n await exec('gnome-screenshot --version');\r\n await exec(`gnome-screenshot -f ${tmpFile}`);\r\n } catch (error) {\r\n // If gnome-screenshot is not available, try using ImageMagick's 'import' command\r\n await exec(`import ${tmpFile}`);\r\n }\r\n }\r\n\r\n return tmpFile;\r\n};\r\n\r\nconst recognizeText = async (filePath, language) => {\r\n const { createWorker } = await npm(\"tesseract.js\");\r\n const worker = await createWorker();\r\n\r\n await worker.loadLanguage(language);\r\n await worker.initialize(language);\r\n\r\n const { data } = await worker.recognize(filePath);\r\n\r\n await worker.terminate();\r\n\r\n return data.text;\r\n};\r\n\r\nconst languages = [\r\n { name: \"Spanish\", value: \"spa\" },\r\n { name: \"French\", value: \"fra\" },\r\n { name: \"Portuguese\", value: \"por\" },\r\n { name: \"English\", value: \"eng\" },\r\n];\r\n//@todo train a model for typescript (https://github.com/tesseract-ocr/tesstrain)\r\n\r\n// if ctrl is pressed, show a modal to select a language\r\nconst selectedLanguage = flag.ctrl\r\n ? await arg(\"Select a language:\", languages)\r\n : \"eng\";\r\n\r\n// Hide the Kit modal before capturing the screenshot\r\nawait hide();\r\n\r\nconst filePath = await captureScreenshot();\r\nif (!await pathExists(filePath)) exit()\r\n\r\nconst text = await recognizeText(filePath, selectedLanguage);\r\n\r\nif (text) {\r\n await clipboard.writeText(text.trim());\r\n await notify(\"Text recognized and copied to clipboard\");\r\n} else {\r\n await notify(\"No text found in the screenshot\");\r\n}\r\n\r\n// Clean up temporary file\r\nawait remove(filePath);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1202","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1201","url":"https://github.com/johnlindquist/kit/discussions/1201","title":"Toggle Screen Lock Macos","name":"Toggle Screen Lock Macos","extension":".md","description":"Created by ElTacitos","resourcePath":"/johnlindquist/kit/discussions/1201","createdAt":"2023-04-24T21:34:45Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjp_","body":"\r\n[Open toggle-screen-lock in Script Kit](https://scriptkit.com/api/new?name=toggle-screen-lock&url=https://gist.githubusercontent.com/ElTacitos/7bb758f516e8e3bc5e1085e306bb0f31/raw/30f26138ddbe17626f1b47c2f2e2c20fa45749b6/toggle-screen-lock.js\")\r\n\r\n```js\r\n// Name: Toggle Screen Lock\r\n// Description: Toggle screen lock on macos (never or 2 minutes)\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet password = await arg({\r\n placeholder: \"Enter sudo password\",\r\n secret: true\r\n})\r\n\r\nconst resp = await exec(`echo ${password} | sudo -S pmset -g | grep displaysleep`)\r\nconst currentSleep = resp.stdout.trimStart().trimEnd().replace( /\\s\\s+/g, ' ' ).split(/\\s/)[1]\r\nconst user = (await exec(`whoami`)).stdout\r\n\r\nif (currentSleep === \"0\") {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 2`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 60`)\r\n await notify(\"Enabled screen lock\")\r\n} else {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 0`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 0`)\r\n await notify(\"Disabled screen lock\")\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1201","img":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/23535629?u=0174ab792e2efe02dfeeddacdf991e267e0a79fe&v=4","user":"Jossdz","author":"Jose Carlos Correa","twitter":"JossDz","discussion":"https://github.com/johnlindquist/kit/discussions/1192","url":"https://github.com/johnlindquist/kit/discussions/1192","title":"Executing teminal commands on WSL(Windows subsystem for Linux)","name":"Executing teminal commands on WSL(Windows subsystem for Linux)","extension":".md","description":"Created by Jossdz","resourcePath":"/johnlindquist/kit/discussions/1192","createdAt":"2023-04-15T23:57:50Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATbHc","body":"Hey Folks,\r\n\r\nI've been working with Script Kit and encountered concerns regarding my local environment. Specifically, since I'm using Linux within Windows through WSL2 (Windows Subsystem for Linux), I was worried about executing the necessary commands for my workflow.\r\n\r\nUpon contacting John, he suggested using the following environment variable to enable command execution in WSL:\r\n\r\n```env\r\n# The value should be the full path to wsl, this is what is needed on windows 11.\r\nKIT_SHELL=C:\\Windows\\System32\\wsl.exe\r\n```\r\n\r\nAfter implementing this variable, I was able to run my commands in WSL. This is particularly useful for me as I can now start my docker environment with a single command.","value":"https://github.com/johnlindquist/kit/discussions/1192","img":"https://avatars.githubusercontent.com/u/23535629?u=0174ab792e2efe02dfeeddacdf991e267e0a79fe&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","author":"Kent C. Dodds","twitter":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1190","url":"https://github.com/johnlindquist/kit/discussions/1190","title":"Extract text from images","name":"Extract text from images","extension":".md","description":"Created by kentcdodds","resourcePath":"/johnlindquist/kit/discussions/1190","createdAt":"2023-04-14T15:23:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATaLp","body":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/kentcdodds/9d49b047079acb3a2e133f7a55fd1837/raw/a7c04475d41460bc8addfa3132f19244691726f3/ocr.ts\")\r\n\r\n```js\r\n// Menu: Optical Character Recognition\r\n// Description: Extract text from images\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport Tesseract from 'tesseract.js'\r\n\r\nconst clipboardImage = await clipboard.readImage()\r\n\r\nif (clipboardImage.byteLength) {\r\n const {data} = await Tesseract.recognize(clipboardImage, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n} else {\r\n let selectedFiles = await getSelectedFile()\r\n let filePaths: Array\r\n\r\n if (selectedFiles) {\r\n filePaths = selectedFiles.split('\\n')\r\n } else {\r\n let droppedFiles = await drop({placeholder: 'Drop images to compress'})\r\n filePaths = droppedFiles.map(file => file.path)\r\n }\r\n for (const filePath of filePaths) {\r\n const {data} = await Tesseract.recognize(filePath, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n }\r\n}\r\n\r\nnotify({\r\n title: 'OCR finished',\r\n message: `Copied text to your clipboard`,\r\n})\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1190","img":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","author":"Kostas Minaidis","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1187","url":"https://github.com/johnlindquist/kit/discussions/1187","title":"Get the price of Bitcoin using the BitFinex open API","name":"Get the price of Bitcoin using the BitFinex open API","extension":".md","description":"Created by kostasx","resourcePath":"/johnlindquist/kit/discussions/1187","createdAt":"2023-04-09T20:58:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATUEu","body":"```js\r\n// Name: BitCoinPrice\r\n// Description: Get latest Bitcoin price using the Bitfinex open API\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet response = await get(`https://api.bitfinex.com/v1/pubticker/BTCUSD`, {\r\n headers: {\r\n Accept: \"text/plain\",\r\n },\r\n})\r\n\r\nconst data = response.data\r\nawait div(`\r\n
\r\n
Price: $${data.last_price}
\r\n
\r\n`)\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1187","img":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","author":"Rohit Kumar Saini","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1183","url":"https://github.com/johnlindquist/kit/discussions/1183","title":"Script to open your links in browser","name":"Script to open your links in browser","extension":".md","description":"Created by rockingrohit9639","resourcePath":"/johnlindquist/kit/discussions/1183","createdAt":"2023-04-04T06:47:52Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATPCd","body":"Hey there,\r\nI have created a script which can open your pre-added links in the browser after selecting from the choices.\r\nFor now, it is just a simple script with hardcoded links, in future we can use `db` to store the links for users.\r\n\r\nHere is the script code -\r\n```js\r\n// Name: Open My Links\r\n// Shortcut: cmd shift l\r\n// Author: Rohit Saini\r\n// GitHub: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst LINKS = {\r\n Github: \"https://github.com/rockingrohit9639\",\r\n LinkedIn: \"https://www.linkedin.com/in/rohit-kumar-saini/\",\r\n} as const;\r\n\r\nconst CHOICES: (keyof typeof LINKS)[] = [\r\n \"Github\",\r\n \"LinkedIn\",\r\n];\r\n\r\nconst linkTitle = await arg(\"Which link to open?\", CHOICES);\r\nconst link = LINKS[linkTitle];\r\nconst command = `open ${link}`;\r\nexec(command);\r\n\r\n```\r\n\r\nHere is the demo of the script - \r\n[link open script.webm](https://user-images.githubusercontent.com/40729749/229710396-04ea1030-354f-4ee3-a08c-c9b2a4d55ca2.webm)\r\n","value":"https://github.com/johnlindquist/kit/discussions/1183","img":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1178","url":"https://github.com/johnlindquist/kit/discussions/1178","title":"Script Kit 1.53.22 - April 2023 Release","name":"Script Kit 1.53.22 - April 2023 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1178","createdAt":"2023-03-25T22:46:01Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ATFov","body":"# Script Kit 1.53.22 - April 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Features\r\n\r\n### `await mic()`\r\n\r\nUsing await mic will return a buffer of the audio recorded from your microphone.\r\n\r\n```js\r\n// Name: Transcribe Mic\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Configuration, OpenAIApi } = await import(\"openai\")\r\n\r\nlet config = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n})\r\n\r\nlet openai = new OpenAIApi(config)\r\n\r\nlet data = await mic()\r\n\r\nlet stream = Readable.from(data)\r\n// https://github.com/openai/openai-node/issues/77#issuecomment-1463150451\r\nstream.path = \"speech.webm\"\r\n\r\n// If you're confused by \"createTranscription\" params, see: https://github.com/openai/openai-node/issues/93#issuecomment-1471285341\r\nlet response = await openai.createTranscription(stream, \"whisper-1\")\r\n\r\nlet transcriptionPath = tmpPath(`${Date.now()}.txt`)\r\nawait writeFile(transcriptionPath, response.data.text)\r\n\r\nawait editor(response.data.text)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png)\r\n\r\n\r\n### `await webcam()`\r\n\r\nUsing await webcam will return a buffer of the image captured from your webcam.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png)\r\n\r\n```js\r\n// Name: Selfie\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await webcam()\r\n\r\nlet filePath = tmpPath(\"webcam.png\")\r\n\r\nawait writeFile(filePath, buffer)\r\nawait revealFile(filePath)\r\n```\r\n\r\n### Resizing\r\n\r\nThe prompt will now grow and shink to match the length of the list of options.\r\n\r\nYou can also manually control the size of prompts by using the new `PROMPT` constants:\r\n\r\n```js\r\nawait editor({\r\n width: PROMPT.WIDTH[\"5XL\"],\r\n height: PROMPT.HEIGHT[\"5XL\"],\r\n})\r\n```\r\n\r\n### Terminal \"Close on Exit\"\r\n\r\nThe terminal will now automatically close when you exit the command.\r\n\r\n> Note: By default, it start a shell (zsh/bash/cmd.exe for mac/linux/windows), so you'll need to run `&& exit` after your command\r\n\r\n```js\r\nawait term(\"brew install ffmpeg && exit\")\r\n```\r\n\r\nYou can also use the `shell` option to disable the shell and just run the tool directly:\r\n\r\n```js\r\nawait term({\r\n shell: false,\r\n command: \"brew\",\r\n args: [\"services\", \"restart\", \"yabai\"],\r\n closeOnExit: false, // optional, if you want to view output before it closes\r\n})\r\n```\r\n\r\n> Note: Also fixed some resizing bugs with the terminal.\r\n\r\n### Windows App Launcher\r\n\r\nPress `;` from the main menu to launch the App Launcher.\r\n\r\nThanks to @dodgez: https://github.com/johnlindquist/kit/pull/1161\r\n\r\n> Note: We still need a Linux App Launcher if anyone wants to take a stab at it :)\r\n\r\n### Custom Fonts\r\n\r\nYou can now use custom fonts with the UI. In your `~/.kenv/.env` file, add:\r\n\r\n```bash\r\nKIT_MONO_FONT=\"Menlo\"\r\nKIT_SANS_FONT=\"Arial\"\r\n```\r\n\r\n### Experimental: Shebang Scripts 🎉\r\n\r\nAdd a script in your `~/.kenv/scripts` directory with a shebang:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png)\r\n\r\n```bash\r\n#!/bin/bash\r\nopen \"https://scriptkit.com\"\r\n```\r\n\r\nThen run it from the main menu\r\n\r\n### Experimental: Windows `.bat` Files\r\n\r\nMac and Linux users have always had executables they could run in the terminal in the `~/.kenv/bin` dircetory. Now, on Windows, Script Kit will generate `.bat` files associated with each script that you can run from the terminal.\r\n\r\n### Info Choices\r\n\r\nWe now have a custom \"info\" choice that isn't selectable, but shows up in the list to present the user with additional information about the input/list:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png)\r\n\r\n```js\r\n// Name: Testing Info OnNoChoices\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"Select a number\",\r\n [\r\n {\r\n name: \"Sorry, no matches\",\r\n info: \"onNoChoices\", // or \"always\"\r\n },\r\n \"one\",\r\n \"two\",\r\n \"three\",\r\n ]\r\n)\r\n```\r\n\r\n### Removing `npm` requirement\r\n\r\nWhen you run a script with a missing module, it will catch the error and prompt you to install it. This removes the requirement of the `await npm()`, but you can still use it if you find it more convenient.\r\n\r\nThe examples and scripts that I share from now on will no longer use `await npm()`, but I have no plans to deprecate it.\r\n\r\n## Fixes\r\n\r\n- Lots of little improvements around the `chat` component.\r\n- Some solid performance improvements\r\n- Upgrading to lots of the latest libraries\r\n\r\n\r\n## Call for Help: Disable Window Animation on Windows?\r\n\r\nIf anyone knows how to disable the window animation on Windows (the one that transparently zooms in when you open a window), please let me know because I think it's super annoying and I'd love to disable it in the app. For now, I strongly recommend disabling it globally on Windows:\r\n\r\n1. Open the Settings app by pressing the Windows key + I.\r\n2. Click on “Ease of Access”.\r\n3. Scroll down to “Other options” and click on “Visual options”.\r\n4. Under “Play animations in Windows”, toggle the switch to “Off”.\r\n\r\n## One Last Thing...\r\n\r\nScript Kit AI course part 1 dropping this week... 💥","value":"https://github.com/johnlindquist/kit/discussions/1178","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1176","url":"https://github.com/johnlindquist/kit/discussions/1176","title":"Force paste into inputs that don't allow it","name":"Force paste into inputs that don't allow it","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1176","createdAt":"2023-03-24T20:59:24Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATFDT","body":"\r\n[Open force-paste in Script Kit](https://scriptkit.com/api/new?name=force-paste&url=https://gist.githubusercontent.com/trevor-atlas/79a688107d6a3362e23adc58f4cce6ed/raw/5d195be171e8356389bd03ce3c9f55109a3fa7ef/force-paste.ts\")\r\n\r\n```js\r\n// Name: force paste\r\n// Description: Paste the contents of your clipboard, even in fields that wouldn't let you paste\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// test it out on the email field here: https://codepen.io/andersschmidt/pen/kOOMmw\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\nawait applescript(`tell application \"System Events\" to keystroke the clipboard as text`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1176","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1173","url":"https://github.com/johnlindquist/kit/discussions/1173","title":"Paste Clipboard Image as Cloudinary Markdown URL","name":"Paste Clipboard Image as Cloudinary Markdown URL","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1173","createdAt":"2023-03-21T17:26:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATB8T","body":"\r\n[Open paste-image-as-url in Script Kit](https://scriptkit.com/api/new?name=paste-image-as-url&url=https://gist.githubusercontent.com/johnlindquist/3593a0bee037b38c23d216191c4e5d7e/raw/b203b5a826d597ffa9e158db06b1ae6757222569/paste-image-as-url.js\")\r\n\r\n```js\r\n// Name: Paste Clipboard Image as Cloudinary Markdown URL\r\n// Shortcut: opt shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await clipboard.readImage()\r\n\r\nif (buffer && buffer.length) {\r\n let { default: cloudinary } = await npm(\"cloudinary\")\r\n\r\n cloudinary.config({\r\n cloud_name: await env(\"CLOUDINARY_CLOUD_NAME\"),\r\n api_key: await env(\"CLOUDINARY_API_KEY\"),\r\n api_secret: await env(\"CLOUDINARY_API_SECRET\"),\r\n })\r\n\r\n let response = await new Promise((response, reject) => {\r\n let cloudStream = cloudinary.v2.uploader.upload_stream(\r\n {\r\n folder: \"clipboard\",\r\n },\r\n (error, result) => {\r\n if (error) {\r\n reject(error)\r\n } else {\r\n response(result)\r\n }\r\n }\r\n )\r\n\r\n new Readable({\r\n read() {\r\n this.push(buffer)\r\n this.push(null)\r\n },\r\n }).pipe(cloudStream)\r\n })\r\n\r\n log(response)\r\n\r\n // format however you want\r\n let markdown = `![${response.url}](${response.url})`\r\n await setSelectedText(markdown)\r\n} else {\r\n await div(md(`# No Image in Clipboard`))\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1173","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","author":"Kostas Minaidis","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1171","url":"https://github.com/johnlindquist/kit/discussions/1171","title":"Find Duplicate Files","name":"Find Duplicate Files","extension":".md","description":"Created by kostasx","resourcePath":"/johnlindquist/kit/discussions/1171","createdAt":"2023-03-18T21:42:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS_R2","body":"**Find duplicate files in a folder (first-level only) using MD5 hash:**\r\n\r\n```js\r\n// Name: FindDuplicate\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n// Supports: Mac\r\nimport \"@johnlindquist/kit\"\r\nimport fs from \"fs\"\r\nimport crypto from \"crypto\"\r\n\r\nconst folder = await drop();\r\nconst dir = await readdir(folder[0].path)\r\nlet content = `\r\n| Filename | MD5 Hash |\r\n| -------- | -------- |\r\n`;\r\nconst hashes = {}\r\ndir.forEach(file => {\r\n const fullPath = `${folder[0].path}/${file}`\r\n\r\n const stats = fs.statSync(fullPath);\r\n if (stats.isDirectory()) { return; }\r\n\r\n const fileData = fs.readFileSync(fullPath)\r\n const hash = crypto.createHash('md5').update(fileData).digest('hex')\r\n if (hashes[hash]) {\r\n return hashes[hash].push(file)\r\n } \r\n hashes[hash] = [file]\r\n})\r\n\r\nObject.entries(hashes).forEach(([hash, listOfFiles]) => {\r\n if (listOfFiles.length > 1) {\r\n listOfFiles.forEach(file => {\r\n content += `| ${file} | ${hash.slice(0,4) + \"...\" + hash.slice(-4)} |\\n`\r\n })\r\n }\r\n})\r\n\r\nawait div(md(content))\r\n\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1171","img":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1169","url":"https://github.com/johnlindquist/kit/discussions/1169","title":"Get a random icebreaker question","name":"Get a random icebreaker question","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1169","createdAt":"2023-03-17T20:23:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS-vH","body":"\r\n[Open icebreaker in Script Kit](https://scriptkit.com/api/new?name=icebreaker&url=https://gist.githubusercontent.com/trevor-atlas/5eea582ea68faf7a4aa68d1f6ee487bd/raw/9acdba7e0276a9aef96f608dde594f445897e013/icebreaker.ts\")\r\n\r\n```js\r\n// Menu: Icebreaker\r\n// Description: Get a random icebreaker question\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst dbvalues = await db('icebreakers');\r\nconst icebreakers: string[] = dbvalues.data;\r\n\r\nconst getRandomElement = (arr: T[]) => {\r\n const index = Math.floor(Math.random() * arr.length);\r\n return arr[index];\r\n};\r\n\r\nconst item = getRandomElement(icebreakers);\r\n\r\nawait div(\r\n `\r\n
\r\n
${item}
\r\n
\r\n `\r\n);\r\n\r\n```\r\n\r\n\r\nAdd a `json` array of icebreaker questions in the `kenv` `db` folder called `icebreakers.json`\r\nFor example\r\n```json\r\n[\r\n \"Show us the weirdest thing you have in the room with you right now.\",\r\n \"There is a free, round-trip shuttle to Mars. The catch: it will take one year of your life to go, visit, and come back. Are you in?\",\r\n \"What is your least favorite thing about technology?\",\r\n \"What superpower would you most want?\",\r\n \"What food is best with cheese?\",\r\n \"Would you go in the mother-ship with aliens if they landed on Earth tomorrow?\",\r\n \"Would you join a community in space if it was permanent?\",\r\n \"Would you rather live 100 years in the past or 100 years in the future?\",\r\n \"You are the best criminal mastermind in the world. What crime would you commit if you knew you would get away with it?\",\r\n \"You can only eat one food again for the rest of your life. What is it?\",\r\n \"You can visit any fictional time or place. Which would you pick?\",\r\n \"In your time as a student in K-12, what made an impact on you. Not who, but what? What do you remember that influenced you today?\",\r\n \"How would you hide a giraffe from the government?\",\r\n \"If you were an inanimate object, what would you be and why?\",\r\n \"What is the most trivial thing about which you have a strong opinion?\",\r\n \"What is the smallest thing for which you are grateful?\",\r\n \"If you could change one thing about yourself physically, what would you change?\",\r\n \"What single event or decision do you think most affected the rest of your life?\",\r\n \"What do you fear, despite having no real reason to do so? Basically, what is an irrational fear you have?\",\r\n \"Do you have any conspiracy theories? If so, what are they?\",\r\n \"What scientific or technological advance blows your mind? Is there any technology that seems so futuristic and advanced you're surprised it actually exists?\",\r\n \"What is something you don't realise is weird until you really think about it?\",\r\n \"You can transport one furious elephant into any point in history, where would you put it?\",\r\n \"If you could make one thing that is now legal, illegal, and one thing that is illegal, legal, what laws would change?\",\r\n \"Would you agree to go without showering, brushing your teeth, and using deodorant for six months to win $500,000? You are not allowed to talk about the deal with anyone until the six months end, or the offer is gone.\",\r\n \"What's the best trip (traveling wise) you ever had?\",\r\n \"Does pineapple go on pizza?\",\r\n \"If you could live anywhere in the world for a year, where would it be?\",\r\n \"What's your favorite seat on an airplane?\",\r\n \"What is your spirit animal? (The animal who is most similar to your personality.)\",\r\n \"What is your favorite thing to do by yourself?\",\r\n \"Have you ever experienced a natural disaster like a hurricane or tornado?\",\r\n \"If you had to delete all but 3 apps from your smartphone, which ones would you keep? (Three apps that have changed your life.)\",\r\n \"If you had to choose between only having a cell phone or a car for the rest of your life, which would you choose?\",\r\n \"What is your favorite tv series?\",\r\n \"What is your favorite book?\",\r\n \"How would you change your life today if the average life expectancy was 400 years?\",\r\n \"A genie grants you three wishes but none of them can directly benefit you. What would those wishes be?\",\r\n \"What is your favorite smell and why?\",\r\n \"According to you, what is the most mind-numbingly dull movie ever made?\",\r\n \"If given the choice of having a talk show host narrate your life, who would you choose?\",\r\n \"Which reality TV show is your guilty pleasure?\",\r\n \"All in all, the movie that had the most significant impact on your life and why?\",\r\n \"If you could switch your life with any fictional character, who would it be?\",\r\n \"Decidedly, you must choose a fictional world that'll become the new reality. Which one would you pick?\",\r\n \"According to you, what is the most monotonous sport to watch?\",\r\n \"Who would be the first celebrity guest in your very own talk show?\",\r\n \"Without a doubt, who is the greatest actor that has ever graced the world?\",\r\n \"If you had the chance to be in the Olympics, which sport would you compete in?\",\r\n \"Generally, which real life person are you most inspired by?\",\r\n \"What's the most underrated actor that you know of?\",\r\n \"What is your 'I wish I had started doing this earlier in my life'?\",\r\n \"What is the coolest website you've ever visited?\",\r\n \"What is your favorite polite insult?\"\r\n]\r\n```\r\n\r\nUse the script!\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1169","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1164","url":"https://github.com/johnlindquist/kit/discussions/1164","title":"A script that asks you to enter a url. it opens it, and takes a small screenshot of it.","name":"A script that asks you to enter a url. it opens it, and takes a small screenshot of it.","extension":".md","description":"Created by SimplGy","resourcePath":"/johnlindquist/kit/discussions/1164","createdAt":"2023-03-12T01:24:50Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS5Nk","body":"\r\n[Open screenshot-url in Script Kit](https://scriptkit.com/api/new?name=screenshot-url&url=https://gist.githubusercontent.com/SimplGy/0e89bc0a60548b32cac9c0db806d9cd4/raw/149f2b5018177cc777bed207b9dc89f664fe54d8/screenshot-url.ts\")\r\n\r\n```js\r\n// Name: Screenshot URL\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\n// get URL from user\r\nlet urlFromUser = await arg(\"Enter the URL to screenshot\");\r\nif (!urlFromUser.match(/^https?:\\/\\//)) {\r\n urlFromUser = `http://${urlFromUser}`;\r\n}\r\nconst pathObj = path.parse(urlFromUser);\r\nlog(pathObj);\r\n\r\n// config\r\nlet timeout = 5_000;\r\nconst FOLDER = 'Downloads/screenshot-url';\r\nconst screenshotFolder = home(FOLDER);\r\nconst filename = `${pathObj.name}${pathObj.ext}.png`\r\nconst screenshotPath = home(FOLDER, filename);\r\n\r\n// Open the window\r\nconst browser = await chromium.launch({ timeout, headless: false });\r\nconst context = await browser.newContext({ colorScheme: \"dark\" });\r\nconst page = await context.newPage();\r\nawait page.setViewportSize({\r\n width: 800,\r\n height: 600,\r\n});\r\npage.setDefaultTimeout(timeout);\r\n\r\ntry {\r\n // docs: https://playwright.dev/docs/api/class-page\r\n await page.goto(urlFromUser);\r\n await page.screenshot({ path: screenshotPath })\r\n \r\n // TODO: shrink the file to a thumbnail\r\n\r\n await revealFile(screenshotFolder)\r\n log(`Done`)\r\n\r\n} catch (error) {\r\n warn('error', error);\r\n}\r\n\r\nawait browser.close();\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1164","img":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/127610787?u=b01a40872f605669b97bb3f31c0a622988ccd019&v=4","user":"laura-ok","author":"Laura Okamoto","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1163","url":"https://github.com/johnlindquist/kit/discussions/1163","title":"natural language shell command","name":"natural language shell command","extension":".md","description":"Created by laura-ok","resourcePath":"/johnlindquist/kit/discussions/1163","createdAt":"2023-03-11T15:02:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS4_I","body":"\r\n[Open natural-language-shell-command in Script Kit](https://scriptkit.com/api/new?name=natural-language-shell-command&url=https://gist.githubusercontent.com/laura-ok/f2cc8d4cfb1211ffc7a494e8f89fff80/raw/04e765fc3b70d0705e874ed62ee16125798394b0/natural-language-shell-command.js\")\r\n\r\n```js\r\n// Name: Natural Language Shell Command\r\n// Description: Convert a natural language command to a shell command\r\n// Author: Laura Okamoto\r\n// Twitter: @laura_okamoto\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { Configuration, OpenAIApi } = await npm(\"openai\");\r\n\r\nconst configuration = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n});\r\nconst openAI = new OpenAIApi(configuration);\r\n\r\nconst res = await arg(\"Describe the shell command you want to run\");\r\nconst prompt = `Use the following shell command to \"${res}\":`;\r\nconst completion = await openAI.createCompletion({\r\n model: \"text-davinci-003\",\r\n prompt,\r\n temperature: 0,\r\n max_tokens: 4069,\r\n});\r\n\r\nsetSelectedText(completion.data.choices[0].text.trim());\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1163","img":"https://avatars.githubusercontent.com/u/127610787?u=b01a40872f605669b97bb3f31c0a622988ccd019&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","author":"Josh Davenport-Smith","twitter":"jdprts","discussion":"https://github.com/johnlindquist/kit/discussions/1162","url":"https://github.com/johnlindquist/kit/discussions/1162","title":"Preview CSS Color","name":"Preview CSS Color","extension":".md","description":"Created by joshdavenport","resourcePath":"/johnlindquist/kit/discussions/1162","createdAt":"2023-03-11T12:24:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS46m","body":"![image](https://user-images.githubusercontent.com/757828/224484149-4376fcf8-bce5-4adb-a2e0-bb0e9389a42a.png)\r\n\r\n[Open preview-css-color in Script Kit](https://scriptkit.com/api/new?name=preview-css-color&url=https://gist.githubusercontent.com/joshdavenport/86cb857671226ea6fb530c6bd7923bdf/raw/8ffbbe2c3ca55d72a54233d39c88095d338adade/preview-css-color.ts\")\r\n\r\n```js\r\n// Name: Preview CSS Color\r\n// Description: Preview any CSS color accepted by background-color\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst color = await arg(\"Color\");\r\n\r\nawait div(`\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
${color}
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1162","img":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","author":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1160","url":"https://github.com/johnlindquist/kit/discussions/1160","title":"Units Convert","name":"Units Convert","extension":".md","description":"Created by Vedinsoh","resourcePath":"/johnlindquist/kit/discussions/1160","createdAt":"2023-03-07T18:09:07Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS1sV","body":"\r\n[Open units-convert in Script Kit](https://scriptkit.com/api/new?name=units-convert&url=https://gist.githubusercontent.com/Vedinsoh/40e74c82f688a80849da32afde7a5130/raw/2004a082fc3a3142de9ae8510fd42569713ae50e/units-convert.js\")\r\n\r\n```js\r\n// Name: Units Convert\r\n// Description: Convert between metric and imperial units\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst convert = await npm(\"convert-units\");\r\n\r\nconst getAllPossibilities = (unit) => {\r\n const possibilities = unit\r\n ? convert().from(unit).possibilities()\r\n : convert().possibilities();\r\n\r\n return possibilities\r\n .map((u) => {\r\n const uDetails = convert().describe(u);\r\n return {\r\n name: `${u} - ${uDetails.plural}`,\r\n value: u,\r\n };\r\n })\r\n .sort((a, b) => {\r\n const aDetails = convert().describe(a.value);\r\n const bDetails = convert().describe(b.value);\r\n if (aDetails.system === bDetails.system) {\r\n return aDetails.value - bDetails.value;\r\n }\r\n return aDetails.system - bDetails.system;\r\n });\r\n};\r\n\r\nconst getUnitString = (unit) => {\r\n const unitDetails = convert().describe(unit);\r\n return `${unitDetails.plural} (${unit})`;\r\n};\r\n\r\nconst convertUnits = (from, to, amount) => {\r\n return String(convert(amount).from(from).to(to));\r\n};\r\n\r\nconst fromUnit = await arg({\r\n placeholder: \"From\",\r\n choices: getAllPossibilities(),\r\n enter: \"To\",\r\n});\r\n\r\nconst toUnit = await arg({\r\n placeholder: \"To\",\r\n choices: getAllPossibilities(fromUnit),\r\n enter: \"Amount\",\r\n hint: `Convert from ${fromUnit} to...`,\r\n});\r\n\r\nawait arg({\r\n placeholder: \"Amount\",\r\n type: \"number\",\r\n enter: \"Exit\",\r\n hint: `${getUnitString(fromUnit)} equals...`,\r\n onInput: (input) => {\r\n const result = convertUnits(fromUnit, toUnit, input);\r\n setPanel(md(`# ${result} ${getUnitString(toUnit)}`));\r\n },\r\n shortcuts: [\r\n {\r\n name: \"Copy result\",\r\n key: `${cmd}+c`,\r\n onPress: (input) => {\r\n copy(convertUnits(fromUnit, toUnit, input));\r\n },\r\n bar: \"right\",\r\n },\r\n ],\r\n});\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1160","img":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","author":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1158","url":"https://github.com/johnlindquist/kit/discussions/1158","title":"IP & Domain Lookup","name":"IP & Domain Lookup","extension":".md","description":"Created by Vedinsoh","resourcePath":"/johnlindquist/kit/discussions/1158","createdAt":"2023-03-07T15:26:33Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS1jz","body":"\r\n[Open ip-lookup in Script Kit](https://scriptkit.com/api/new?name=ip-lookup&url=https://gist.githubusercontent.com/Vedinsoh/140a888222f85f8a8da1e65fdbdd87bb/raw/9ad2f7dedacc718ab906fcad75fc43c9c3b05451/ip-lookup.js\")\r\n\r\n```js\r\n// Name: IP & Domain Lookup\r\n// Description: Get information about an IP address or domain\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport net from \"node:net\";\r\nimport { URL } from \"node:url\";\r\n\r\nconst getLookupData = async (query) => {\r\n // Reference: https://ip-api.com/docs/api:json\r\n const response = await get(\r\n `http://ip-api.com/json/${query}?fields=status,message,continent,country,countryCode,regionName,city,zip,lat,lon,timezone,isp,org,as,query`\r\n );\r\n\r\n if (response.data.status === \"fail\") {\r\n throw new Error(response.data.message);\r\n }\r\n\r\n return response.data;\r\n};\r\n\r\nlet lookupQuery = await arg({\r\n placeholder: \"Enter IP address or domain\",\r\n validate: (value) => {\r\n if (net.isIP(value) !== 0) {\r\n return true;\r\n } else {\r\n try {\r\n new URL(`https://${value}`);\r\n return true;\r\n } catch (e) {\r\n return \"Please enter a valid IP address or domain\";\r\n }\r\n }\r\n },\r\n});\r\n\r\nconst data = await getLookupData(lookupQuery);\r\n\r\ndiv(\r\n md(`\r\n# IP Lookup: ${lookupQuery}\r\n\r\n- **IP:** ${data.query}\r\n- **ISP:** ${data.isp}\r\n- **Organization:** ${data.org}\r\n- **AS:** ${data.as}\r\n- **Continent:** ${data.continent}\r\n- **Country:** ${data.country} (${data.countryCode})\r\n- **Region:** ${data.regionName}\r\n- **City:** ${data.city}\r\n- **Zip Code:** ${data.zip}\r\n- **Latitude:** ${data.lat}\r\n- **Longitude:** ${data.lon}\r\n- **Timezone:** ${data.timezone}\r\n`)\r\n);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1158","img":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/4293840?v=4","user":"fischgeek","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1154","url":"https://github.com/johnlindquist/kit/discussions/1154","title":"Simple Snippet Creator","name":"Simple Snippet Creator","extension":".md","description":"Created by fischgeek","resourcePath":"/johnlindquist/kit/discussions/1154","createdAt":"2023-03-05T17:30:25Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASzjM","body":"I wanted a quick way to make very simple text replacements. \r\n\r\n```\r\nlet [txt, rep] = await fields([\"Text\", \"Replacement\"])\r\nlet dir = \"/Users/fischgeek/.kenv/scripts\" // <- Update to your specific path\r\nawait writeFile(`${dir}/${txt}.js`, `//Snippet: ${txt}\\nawait keyboard.type(\"${rep}\")`)\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1154","img":"https://avatars.githubusercontent.com/u/4293840?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/184563?u=27a3324127667282e6eb95d538d5b58b4d007352&v=4","user":"brpaz","author":"Bruno Paz","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1150","url":"https://github.com/johnlindquist/kit/discussions/1150","title":"Raindrop Bookmarks search","name":"Raindrop Bookmarks search","extension":".md","description":"Created by brpaz","resourcePath":"/johnlindquist/kit/discussions/1150","createdAt":"2023-03-04T19:07:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASy0I","body":"Open your [Raindrop](app.raindrop.io/) bookmarks from ScriptKit\r\n\r\n```ts\r\n// Name: Raindrop\r\n// Description: Search your Raindrop.io bookmarks\r\n// Author: Bruno Paz\r\n// Github: @brpaz\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { Choice } from \"@johnlindquist/kit\"\r\n\r\nconst COLLECTION_ID_ALL = 0\r\nconst COLLECTION_ID_UNSORTED = -1\r\n\r\ninterface RaindropResponse {\r\n items: RaindropBookmark[]\r\n}\r\n\r\ninterface RaindropBookmark {\r\n _id: string\r\n title: string\r\n link: string\r\n excerpt: string\r\n tags: string[]\r\n created: string\r\n type: string\r\n}\r\n\r\nconst raindropAPIKey = await env(\"RAINDROP_API_KEY\", {\r\n placeholder: \"Enter your Raindrop.io Test API Key\",\r\n hint: md(\r\n `Get a [Raindrop.io Test API Key](https://app.raindrop.io/settings/integrations)`\r\n ),\r\n\r\n secret: true,\r\n})\r\n\r\nasync function raindropSearch(query: string, collectionId: number): Promise {\r\n const url = `https://api.raindrop.io/rest/v1/raindrops/${collectionId}?search=${query}&access_token=${raindropAPIKey}`\r\n\r\n const response = await get(url)\r\n const data: RaindropResponse = await response.data\r\n\r\n return data.items.map((item) => ({\r\n name: item.title,\r\n description: item.excerpt,\r\n value: item.link,\r\n onSubmit: async () => {\r\n open(item.link)\r\n }\r\n }))\r\n}\r\n\r\nasync function allBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_ALL)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nasync function unsortedBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io unsorted bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_UNSORTED)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nonTab(\"Unsorted\", unsortedBookmarks);\r\nonTab(\"All\", allBookmarks);\r\n``` ","value":"https://github.com/johnlindquist/kit/discussions/1150","img":"https://avatars.githubusercontent.com/u/184563?u=27a3324127667282e6eb95d538d5b58b4d007352&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1148","url":"https://github.com/johnlindquist/kit/discussions/1148","title":"Script Kit v1.51.2 - Welcoming our AI Overlords! 👑","name":"Script Kit v1.51.2 - Welcoming our AI Overlords! 👑","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1148","createdAt":"2023-03-03T19:33:27Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ASyKZ","body":"# Script Kit v1.51.2 - March 2023 Release\r\n\r\nDownload from https://www.scriptkit.com/\r\nJoin our Discord: https://discord.gg/qnUX4XqJQd\r\n❤️ Sponsor Script Kit development [https://github.com/sponsors/johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205) ❤️\r\n## Features\r\n\r\n### Build AI Conversations with the New `chat` Prompt\r\n\r\nYou can use the new `chat` prompt to create a chat interface for your scripts. The `chat` prompt returns a `messages` array that you can inspect to see the messages that were sent and received.\r\n\r\nIt supports keyboard navigation so you can press the up/down arrow keys to navigate through the messages and copy/paste them. You can also use `shortcuts` the same way you would with other prompts.\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/609d3f2dcbc49911698c3c2162310c0d/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n```\r\n\r\nThe `kit-examples` repo (bundled with new installs) has an example of using the latest ChatGPT model:\r\nhttps://github.com/johnlindquist/kit-examples/blob/main/scripts/chatgpt.js\r\n\r\n✨ Want more AI example scripts? Check out this post 👀: https://github.com/johnlindquist/kit/discussions/1143 ✨\r\n\r\n### The `eyeDropper` Color Picker\r\n\r\nYou can now use the `eyeDropper` function to pick a color from anywhere on your screen. It returns an object with `sRGBHex` to align with Chrome's Eyedropper tool.\r\n\r\n> Note: Unfortunately, windows restricts color picking to the foreground window. Still investigating a workaround.\r\n\r\n\r\n[Open testing-get-color in Script Kit](https://scriptkit.com/api/new?name=testing-get-color&url=https://gist.githubusercontent.com/johnlindquist/e10cc5dfc08fd5c9824bb3a7e5e50071/raw/76bb4b86f08e1f2a8b2f09d6e2ba47286dc42673/testing-get-color.ts\")\r\n\r\n```js\r\n// Name: Testing Get Color\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide()\r\nlet value = await eyeDropper()\r\nawait editor(value.sRGBHex)\r\n\r\n```\r\n\r\n### Node 18.12.1\r\n\r\nScript Kit now bundles Node 18.12.1. (Previous scripts ran on Node 16.17.2).\r\n\r\n### New Settings in `~/.kit/db/app.json` Features\r\n\r\n- \"mini\" boolean - Use Script Kit in a much smaller window\r\n- \"termFont\" string - Set the font for the terminal\r\n- \"cachePrompt\" boolean - If you experience issues with the prompt position caching logic, disable it so is _always_ show in the center of the screen\r\n- \"convertKeymap\" boolean - Script Kit auto converts to your selected OS keymap (dvorak, colemak, etc). There have been a few reports of this conflicting with Portuguese keyboards. If you experience issues, disable this setting.\r\n- \"searchDebounce\" boolean - by default, Script Kit will debounce when your choices list has over >1000 items. It's mostly a precaution and you can disable it if you'd like.\r\n\r\nThe defaults are as follows:\r\n```json\r\n{\r\n \"mini\": false,\r\n \"termFont\": \"monospace\",\r\n \"cachePrompt\": true,\r\n \"convertKeymap\": true,\r\n \"searchDebounce\": true\r\n}\r\n```\r\n\r\n## Fixes\r\n\r\n- Fixed a bug where `theme` was reset to the default when waking from sleep\r\n- Fixed the `Open in Script Kit` urls for Windows users\r\n- Fixed Calculator sizing from main menu\r\n- `await npm(\"package-name\")` will now mark a package as an \"external\" dependency in TypeScript so you can use import statements for packages that aren't installed yet and your script will still compile\r\n- Other minor bug fixes\r\n- Fixed installation issues when dealing with certs/proxies\r\n\r\n## Experimental\r\n\r\n### `toast()`\r\n\r\nYou can try out the experimental `toast(\"Copied\")` feature. I'm still working out the API, so there's no \"onClick\" or anything, but you should be able to pass the other toast properties (this uses `react-toastify`) explained [here](https://fkhadra.github.io/react-toastify/dispatch-toast-outside-of-react-component)","value":"https://github.com/johnlindquist/kit/discussions/1148","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1143","url":"https://github.com/johnlindquist/kit/discussions/1143","title":"Script Kit AI Chat Pre-release","name":"Script Kit AI Chat Pre-release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1143","createdAt":"2023-02-25T03:07:13Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ASrSe","body":"\r\nhttps://user-images.githubusercontent.com/36073/221332902-87c2d0a4-46b8-4eb1-a935-9a9fcae90e94.mp4\r\n\r\n## This is a Preview Build for Next Week's Release\r\n\r\nI'm releasing this to gather some feedback about the Chat component before I push the main release in the beginning of March. Please let me know below what you think of the new `chat` component. What do you love? What needs to change? What should be added? Please leave your ideas below! 🙏\r\n\r\nDownload here Pre-release here:\r\n\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.50.6\r\n\r\nJoin our Discord:\r\n\r\nhttps://discord.gg/qnUX4XqJQd\r\n\r\n## Chat Component Hello World\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/0f0f86d0dc84f7acb30dc726da39b470/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n\r\n## Chat Component with AI\r\n\r\n[Open openai-chat in Script Kit](https://scriptkit.com/api/new?name=openai-chat&url=https://gist.githubusercontent.com/johnlindquist/fc9f74aa387e371a59c973175201ead2/raw/e94fc92dec07dda63982bb54e756a35cfdb92532/openai-chat.ts\")\r\n\r\n```js\r\n// Name: OpenAI Chat\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { ConversationChain } = await import(\"langchain/chains\")\r\nlet { BufferMemory } = await import(\"langchain/memory\")\r\n\r\nlet llm = new OpenAI({\r\n openAIApiKey: await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n }),\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet memory = new BufferMemory()\r\nlet chain = new ConversationChain({\r\n llm,\r\n memory,\r\n})\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n await chain.call({ input })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n## Chat with a .txt File\r\n\r\n[Open doc-talk in Script Kit](https://scriptkit.com/api/new?name=doc-talk&url=https://gist.githubusercontent.com/johnlindquist/f1bfd6758815a1fb87d51a9c7893bd2e/raw/9d54e81aa9f269af0a22a44e6bfcb0f77b04f44d/doc-talk.ts\")\r\n\r\n```js\r\n/*\r\n# Doc Talk\r\n\r\nChat with a .txt file such as a book. In chat, ask questions like:\r\n- \"Who are the main characters?\"\r\n- \"Where are the key settings\"?\r\n- \"Summarize the story arc\"\r\n\r\n> Note: The chat's knowledge is limited to the loaded .txt file\r\n*/\r\n\r\n// Name: Doc Talk\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\nawait npm(\"hnswlib-node\")\r\n\r\nlet { OpenAI } = await import(\"langchain/llms\")\r\nlet { VectorDBQAChain } = await import(\"langchain/chains\")\r\nlet { HNSWLib } = await import(\"langchain/vectorstores\")\r\nlet { OpenAIEmbeddings } = await import(\r\n \"langchain/embeddings\"\r\n)\r\nlet { RecursiveCharacterTextSplitter } = await import(\r\n \"langchain/text_splitter\"\r\n)\r\n\r\nlet filePath = await path({\r\n hint: `Select a .txt file to talk to or Download Romeo and Juliet`,\r\n})\r\n\r\nif (filePath === \"__ROMEO__\") {\r\n let buffer = await download(\r\n `https://www.gutenberg.org/cache/epub/1513/pg1513.txt`\r\n )\r\n filePath = home(\"Downloads\", \"romeo-and-juliet.txt\")\r\n await writeFile(filePath, buffer)\r\n}\r\n\r\nwait(250).then(() => setLoading(true))\r\n\r\ndiv(\r\n md(`# Loading...\r\n\r\n~~~\r\nLoading in ${filePath}\r\n~~~\r\n`)\r\n)\r\n\r\nlet text = await readFile(filePath, \"utf-8\")\r\n\r\nconst model = new OpenAI({\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nconst textSplitter = new RecursiveCharacterTextSplitter({\r\n chunkSize: 1000,\r\n})\r\nconst docs = textSplitter.createDocuments([text])\r\n\r\nconst vectorStore = await HNSWLib.fromDocuments(\r\n docs,\r\n new OpenAIEmbeddings()\r\n)\r\n\r\nconst chain = VectorDBQAChain.fromLLM(model, vectorStore)\r\n\r\nsetLoading(false)\r\nlet messages = await chat({\r\n onSubmit: async query => {\r\n const res = await chain.call({\r\n input_documents: docs,\r\n query,\r\n })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Google AI\r\n\r\n\r\n[Open google-ai in Script Kit](https://scriptkit.com/api/new?name=google-ai&url=https://gist.githubusercontent.com/johnlindquist/3086fd1ddb12e16056cf116633f69547/raw/7ded0692a0909bbdd8b80eb7af69a5d2bf3d7352/google-ai.ts\")\r\n\r\n```js\r\n/*\r\n# Google AI Experiment\r\n\r\nThis is 100% experimental. I'm still learning the ins and outs of langchain.\r\nIf you have feedback on how to improve, PLEASE share 🙏\r\n\r\n\\- John Lindquist\r\n */\r\n\r\n// Name: Google AI\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\n// Note: This lib is updated fairly frequently to keep up with Google's changes: https://www.npmjs.com/package/googlethis\r\nawait npm(\"googlethis\")\r\nawait npm(\"@extractus/article-extractor\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { Tool } = await import(\"langchain/tools\")\r\nlet { initializeAgentExecutor } = await import(\r\n \"langchain/agents\"\r\n)\r\n\r\nclass GoogleThis extends Tool {\r\n name = \"search\"\r\n description =\r\n \"a search engine. useful for when you need to answer questions about current events. input should be a search query. Output should include the best result and associated URL.\"\r\n\r\n formatResults = response => {\r\n let data = response?.results\r\n ?.slice(0, 3)\r\n ?.map(r => {\r\n return `\r\ntitle: ${r.title}\r\ndescription: ${r.description}\r\nurl: ${r.url}\r\n`\r\n })\r\n .join(\"\\n\")\r\n\r\n if (response?.knowledge_panel?.title)\r\n data = `Best title: ${response?.knowledge_panel?.title}\r\n${data}`\r\n\r\n if (response?.knowledge_panel?.description)\r\n data = `Best description: ${response?.knowledge_panel?.description}\r\n${data}`\r\n\r\n return data\r\n }\r\n\r\n async call(input: string) {\r\n let google = await import(\"googlethis\")\r\n let response = await google.search(input)\r\n return this.formatResults(response)\r\n }\r\n}\r\n\r\nclass ReadURL extends Tool {\r\n name = \"read\"\r\n description = `a web scraper. Input is a url. Output is the contents of the page.`\r\n\r\n formatArticle = (url, article) => {\r\n let formatted = ``\r\n try {\r\n formatted = Object.entries(article)\r\n .filter(([key, value]) => key && value)\r\n .map(([key, value]) => {\r\n // In case the article contents are too long\r\n // TODO: Should probably wrap over to a \"Document\" paradigm here...\r\n if (typeof value === \"string\") {\r\n return [key, value?.slice(0, 1000) || \"\"]\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return [key, value.join(\",\")]\r\n }\r\n\r\n return [key, value]\r\n })\r\n .map(([key, value]) => `${key}: ${value}`)\r\n .join(\"\\n\")\r\n } catch (error) {\r\n formatted = `Couldn't read the contents of ${url}`\r\n }\r\n return formatted\r\n }\r\n\r\n async call(url: string) {\r\n let { extract } = await import(\r\n \"@extractus/article-extractor\"\r\n )\r\n let article = await extract(url)\r\n let formatted = this.formatArticle(url, article)\r\n\r\n return formatted\r\n }\r\n}\r\n\r\nlet tools = [new GoogleThis(), new ReadURL()]\r\n\r\nlet yankAnswer = async output => {\r\n return output?.generations\r\n ?.at(0)\r\n ?.at(0)\r\n ?.text.split(\"\\n\")\r\n ?.at(-1)\r\n .replace(\"Final Answer: \", \"\")\r\n}\r\n\r\nlet llm = new OpenAI({\r\n temperature: 0.7,\r\n streaming: true,\r\n callbackManager: {\r\n handleError: log,\r\n handleEnd: output => {\r\n let answer = yankAnswer(output)\r\n history = `${history}\r\nAI: ${answer}`\r\n },\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet executor = await initializeAgentExecutor(\r\n tools,\r\n llm,\r\n \"zero-shot-react-description\"\r\n)\r\n\r\n// TODO: I'm sure this can be _vastly_ improved\r\nlet history = `The AI should always include relevant URLs when possible.\r\nThe AI should use the given information and URLs to explain why it chose the answer.\r\nThe AI should avoid commas by formatting with newlines\r\n\r\n`\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n history = `${history}\r\nMe: ${input}`\r\n\r\n await executor.call({ input: history })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Read More about LangChain to Get Started with AI in Script Kit\r\n\r\nhttps://hwchase17.github.io/langchainjs/docs/overview\r\n","value":"https://github.com/johnlindquist/kit/discussions/1143","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/6349395?u=650885c8ac85a967ab8787bfffad65e1206266d1&v=4","user":"abisuq","author":"__","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1142","url":"https://github.com/johnlindquist/kit/discussions/1142","title":"Escape Backticks for copy paste string in javascript","name":"Escape Backticks for copy paste string in javascript","extension":".md","description":"Created by abisuq","resourcePath":"/johnlindquist/kit/discussions/1142","createdAt":"2023-02-24T05:33:20Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASqgw","body":" [Open browse-scriptkit in Script Kit](https://scriptkit.com/api/new?name=browse-scriptkit&url=https://gist.githubusercontent.com/abisuq/a27c130faa4ceb2b582a78b929bde0b2/raw/5d480d52863532d61adbb363fcb217b3f92c2cb8/browse-scriptkit.js\")\r\n\r\n```js\r\n// Name: Escape Backticks\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"Paste text to escape backticks\");\r\nawait copy(text.replace(/`/g, '\\\\`'));\r\n\r\n\r\n\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1142","img":"https://avatars.githubusercontent.com/u/6349395?u=650885c8ac85a967ab8787bfffad65e1206266d1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/60891163?u=b1985aac6c4755041fd849428360e69819e2339c&v=4","user":"AloisCRR","author":"Alois Carrera","twitter":"AloisCRR","discussion":"https://github.com/johnlindquist/kit/discussions/1141","url":"https://github.com/johnlindquist/kit/discussions/1141","title":"Center focused app based on window dimensions","name":"Center focused app based on window dimensions","extension":".md","description":"Created by AloisCRR","resourcePath":"/johnlindquist/kit/discussions/1141","createdAt":"2023-02-23T13:54:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASp0i","body":"\r\n[Open center-app in Script Kit](https://scriptkit.com/api/new?name=center-app&url=https://gist.githubusercontent.com/AloisCRR/4a27c9e04b145be6a32e6ac4fa07894c/raw/f01bffb81564899dc01d7c931d82e072ec1918ba/center-app.js\")\r\n\r\n```js\r\n// Menu: Center App\r\n// Description: Center current focused app based on window size\r\n// Author: Alois Carrera\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst activeScreen = await getActiveScreen()\r\n\r\nconst {\r\n workArea: {\r\n height,\r\n width,\r\n x: workAreaX,\r\n y: workAreaY\r\n }\r\n} = activeScreen\r\n\r\nconst activeAppBounds = await getActiveAppBounds()\r\n\r\nconst { top, left, right, bottom } = activeAppBounds\r\n\r\nconst windowHeight = bottom - top\r\n\r\nconst windowYCenter = windowHeight / 2\r\n\r\nconst windowWidth = right - left\r\n\r\nconst windowXCenter = windowWidth / 2\r\n\r\nsetActiveAppPosition({\r\n x: workAreaX + (width / 2) - windowXCenter,\r\n y: workAreaY + (height / 2) - windowYCenter\r\n})\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1141","img":"https://avatars.githubusercontent.com/u/60891163?u=b1985aac6c4755041fd849428360e69819e2339c&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/830800?v=4","user":"johtso","author":"Johannes","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1126","url":"https://github.com/johnlindquist/kit/discussions/1126","title":"Open daily note in Obsidian","name":"Open daily note in Obsidian","extension":".md","description":"Created by johtso","resourcePath":"/johnlindquist/kit/discussions/1126","createdAt":"2023-02-13T23:20:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AShVy","body":"\r\n[Open daily-note in Script Kit](https://scriptkit.com/api/new?name=daily-note&url=https://gist.githubusercontent.com/johtso/b6a5d6e85d0805dbd25d5a36ffda6abb/raw/ef858f9129001cf187d0eb718108f7d734e2cef6/daily-note.ts\")\r\n\r\n```js\r\n// Name: daily note\r\n// Description: Open today's daily note in obsidian\r\n\r\n// You must install the Actions URI plugin and have the daily notes plugin enabled\r\n// Currently MacOS only\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { homedir } from \"os\"\r\nimport { join as joinPath } from \"path\"\r\n\r\nconst VAULT_NAME = await env(\r\n \"VAULT_NAME\",\r\n async () => {\r\n const vaultNames = await getVaultNames().catch(() => [])\r\n if (vaultNames.length === 1) {\r\n return vaultNames[0]\r\n } else {\r\n return await arg(\r\n \"Which vault do you want to use?\",\r\n vaultNames\r\n )\r\n }\r\n }\r\n);\r\n\r\nconst CREATE_URI = `obsidian://actions-uri/daily-note/create?vault=${VAULT_NAME}&silent=true`\r\nconst OPEN_URI = `obsidian://actions-uri/daily-note/open-current?vault=${VAULT_NAME}`\r\n\r\nawait applescript(`\r\n tell application \"Obsidian\"\r\n open location \"${CREATE_URI}\"\r\n open location \"${OPEN_URI}\"\r\n activate\r\n end tell\r\n`);\r\n\r\nasync function getVaultNames() {\r\n const obsidianConfPath = joinPath(homedir(), \"Library/Application Support/obsidian/obsidian.json\")\r\n\r\n // {\"vaults\":{\"9aeaa3aaa2ad0602\":{\"path\":\"/Users/human/Documents/Obsidian Vault\",\"ts\":1651412412801,\"open\":true}}}\r\n const obsidianConf = JSON.parse(await (await readFile(obsidianConfPath)).toString())\r\n const vaults = obsidianConf.vaults\r\n const vaultNames = Object.keys(vaults).map((vaultId) => vaults[vaultId].path.split(\"/\").pop())\r\n return vaultNames\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1126","img":"https://avatars.githubusercontent.com/u/830800?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1122","url":"https://github.com/johnlindquist/kit/discussions/1122","title":"Anime Search - Working - Updated","name":"Anime Search - Working - Updated","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1122","createdAt":"2023-02-13T01:47:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASgVr","body":"\r\n[Open anime-search in Script Kit](https://scriptkit.com/api/new?name=anime-search&url=https://gist.githubusercontent.com/Ambushfall/c27b170000791f197fcfa5ca154a966b/raw/8bd65a0da0841f83ba892aaa4e3e72649f4283ee/anime-search.js\")\r\n\r\nUpdated Johns amazing script to work with the new v4 Api, and using widget instead of the deprecated showImage method.\r\n\r\nProps to John for making all of this possible!\r\n\r\n[Original script](https://www.scriptkit.com/johnlindquist/anime-search)\r\n\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Menu: Search Anime\r\n// Description: Use the jikan.moe API to search anime\r\n// Author: John Lindquist, Updated by Ambushfall\r\n\r\nlet anime = await arg(\"Anime:\")\r\n\r\nlet response = await get(\r\n `https://api.jikan.moe/v4/anime?q=${anime}`\r\n)\r\n\r\nlet { images, title } = response.data.data[0]\r\n\r\nlet { jpg } = images\r\n\r\nlet { image_url, small_image_url, large_image_url } = jpg\r\n\r\nconst html = `\r\n\r\n
\r\n`;\r\n\r\nlet wg = await widget(html, {\r\n state: {\r\n url: large_image_url\r\n }\r\n})\r\n\r\nwg.onResized(async () => {\r\n wg.fit()\r\n})\r\n\r\n// win32 on-click not working so this does nothing really.\r\n\r\n// TODO: When on click starts working change state to the next result\r\n// wg.onClick((event) => event.targetId === \"x\" ? wg.close() : inspect(event.targetId));\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1122","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1121","url":"https://github.com/johnlindquist/kit/discussions/1121","title":"Part 2: Clipboard special history, preview images as well","name":"Part 2: Clipboard special history, preview images as well","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1121","createdAt":"2023-02-13T00:17:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASgS-","body":"\r\n[Open clipboard-history in Script Kit](https://scriptkit.com/api/new?name=clipboard-history&url=https://gist.githubusercontent.com/Ambushfall/444bb14ca4b5268ea855cec8431b7dfc/raw/3fb4c0962b3cfdf2220fd96f71190e4ec1e872e5/clipboard-history.js\")\r\n\r\n```js\r\n// Menu: Clipboard History\r\n// Description: Copy something from the clipboard history\r\n// Shortcut: command shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { history } = await db(\"clipboard-history\")\r\n\r\nlet { value, type } = await arg(\"What to paste?\", () => {\r\n return history.map(({ value, type, timestamp, secret }) => {\r\n return {\r\n type,\r\n name: secret ? value.slice(0, 4).padEnd(10, \"*\") : value,\r\n value: {\r\n value,\r\n type,\r\n },\r\n description: timestamp,\r\n preview:\r\n type === \"image\"\r\n ? md(`![timestamp](${value})`)\r\n : value.includes(\"\\n\")\r\n ? `
${value\r\n .split(\"\\n\")\r\n .map((line) => `
${line}
`)\r\n .join(\"\")}
`\r\n : null,\r\n }\r\n })\r\n})\r\n\r\nif (type === \"image\") {\r\n await copyPathAsImage(value)\r\n await keystroke(\"command v\")\r\n}\r\n\r\nif (type === \"text\") {\r\n await setSelectedText(value)\r\n}\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1121","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1120","url":"https://github.com/johnlindquist/kit/discussions/1120","title":"Clipboard special history, preview images as well","name":"Clipboard special history, preview images as well","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1120","createdAt":"2023-02-13T00:12:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASgSx","body":"\r\n\r\n\r\n[Open copy-to-clipboard in Script Kit](https://scriptkit.com/api/new?name=copy-to-clipboard&url=https://gist.githubusercontent.com/Ambushfall/db9f545a9099a0a674f14679c862c0c3/raw/f9712e16d1965f16c578ffe4ad0ccb2c1e493086/copy-to-clipboard.js\")\r\n\r\n\r\ncopy-to-clipboard.js\r\n```js\r\n// Menu: Copy to Clipboard\r\n// Description: Save to Clipboard history\r\n// Shortcut: command shift c\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { write, history } = await db(\"clipboard-history\", { history: [{ value: \"\", type: \"\", timestamp: \"\", secret: \"\" }] })\r\n\r\nconst clipboardVal = await clipboard.readText();\r\n\r\n\r\nconst newValue = {\r\n value: clipboardVal,\r\n timestamp: new Date(Date.now()).toLocaleString('en-GB', { timeZone: 'UTC' }),\r\n secret: clipboardVal.includes('secret'),\r\n type: /(http)?s?:?(\\/\\/[^\"']*\\.(?:png|jpg|jpeg|gif|png|svg))/i.test(clipboardVal) ? \"image\" : \"text\"\r\n}\r\n\r\nhistory.push(newValue)\r\n\r\nawait write()\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1120","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1117","url":"https://github.com/johnlindquist/kit/discussions/1117","title":"Today's date as ISO","name":"Today's date as ISO","extension":".md","description":"Created by SimplGy","resourcePath":"/johnlindquist/kit/discussions/1117","createdAt":"2023-02-11T20:44:58Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASfuX","body":"Completely ripped from https://github.com/johnlindquist/kit/discussions/1116 (Thanks Daniel!) I just like the ISO format better.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/SimplGy/2676c4ccad30d9c71f7096422eb5fd44/raw/0da283bff42524450ee9f162c24451e1a8332a47/today-timestamp.ts\")\r\n\r\n```js\r\n// Name: today-timestamp\r\n// Description: inserts today's date in \"ISO\" format 2023-02-11\r\n// Snippet:\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\nconst formatted = `${today.getFullYear()}-${twoDigits(today.getMonth() + 1)}-${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1117","img":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1116","url":"https://github.com/johnlindquist/kit/discussions/1116","title":"Insert current timestamp as YYYY/MM/DD","name":"Insert current timestamp as YYYY/MM/DD","extension":".md","description":"Created by danielo515","resourcePath":"/johnlindquist/kit/discussions/1116","createdAt":"2023-02-11T06:58:35Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASfaI","body":"This snippet is basic, and stupid, but you will be happy to have it around when you need it.\r\nRather than manually input the current today date, you just type `!tday` and you get it.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/danielo515/9356f1af0b642dd23f6cdc188d73d7be/raw/6f8ca2cf7a666762c533bb47773403f210c55f49/today-timestamp.ts\")\r\n\r\n```ts\r\n// Name: today-timestamp\r\n// Description: inserts the today date (not including time) formatted as YYYY/MM/DD\r\n// Snippet: !tday\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\n// Format date to YYYY/MM/DD format\r\nconst formatted = `${today.getFullYear()}/${twoDigits(today.getMonth() + 1 )}/${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1116","img":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1111","url":"https://github.com/johnlindquist/kit/discussions/1111","title":"Ask for user input and transform it to a url encoded string","name":"Ask for user input and transform it to a url encoded string","extension":".md","description":"Created by danielo515","resourcePath":"/johnlindquist/kit/discussions/1111","createdAt":"2023-02-09T15:41:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASd4k","body":"Many times I don't want to url encode what I have on the clipboard, but I want to manually type it. This little script is for that.\r\n\r\n[Open url-encode in Script Kit](https://scriptkit.com/api/new?name=url-encode&url=https://gist.githubusercontent.com/danielo515/b2b2576155111a3a8fb73b47da2efac8/raw/cf81906c75996c52064151670cad363a71c7317d/url-encode.ts\")\r\n\r\n```js\r\n// Name: url encode\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"What do you want to encode\");\r\nconst encoded = encodeURIComponent(text)\r\nawait copy(encoded);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1111","img":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/99090177?u=23294c71201154e9389c53cc961811acb9aff635&v=4","user":"wisskirchenj","author":"Jürgen Wißkirchen","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1110","url":"https://github.com/johnlindquist/kit/discussions/1110","title":"Full text search in (java-)files over all my projects && open hit(s) in IDE","name":"Full text search in (java-)files over all my projects && open hit(s) in IDE","extension":".md","description":"Created by wisskirchenj","resourcePath":"/johnlindquist/kit/discussions/1110","createdAt":"2023-02-08T19:39:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AScxi","body":"My typical usecase: I remember that I used some class (e.g. Mockitos InOrde, CsvSource or ExecutorService) in a similar situation or test and start searching in which project that was. My root contains 30+ projects - so sometimes this took me 15+ min or I had to google again, howto use `find -exec` - and even then it's clumsy.. \r\n\r\nNow this is perfect to me: I enter the search string (class, method, ...) and the surrounding context width in hits (the 'n' in grep -n).\r\nThen a div-container shows me all scrollable hits and I can refresh my mind on what I did years ago. \r\nFinally I hit Return and get a Button-List widget with all filenames with hits, where i can click on. This opens the project in IDEA, opens the file with the hit inside this project and even puts the clipboard copied search string by keystroke into IDEA's search dialog, so I can navigate with arrows...\r\nSuper helpful to me. :smile:\r\n\r\nTo reuse, there is a little customization needed, as I hardcoded my projects root and file pattern *.java. \r\nAlso the exclusion of 'z'-starting directories is sure special to me - but all that should be easy to adapt. \r\nAlso, I am more then happy to help, if there's need.\r\n**Note:** Keystrokes in IDEA at end of script, needs accessibility rights. My platform is MacOS.\r\n\r\n```ts\r\n// Shortcut: cmd opt f\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst smallArg = (placeholder: string) => arg({\r\n placeholder: placeholder,\r\n height: 100,\r\n width: 500\r\n});\r\n\r\nconst substring = await smallArg(\"Substring to search:\");\r\nconst lines = await smallArg(\"# surrounding lines in results:\");\r\n\r\nconst PROJECT_ROOT = \"/Users/jwisskirchen/IdeaProjects\";\r\nconst CLOSE_LABEL = \"Close\";\r\n\r\n// execute find java-files on all project-subdirs not starting with 'z' (as those are special ones...)\r\nconst results = await $`cd ${PROJECT_ROOT} ; find [^z]* -name *.java -exec grep -q ${substring} {} ';' -exec echo \"******{}******\" ';' -exec grep -${lines} ${substring} {} ';'`;\r\n\r\n// Split filepaths and search results in tokens-array, replace '<' as this confuses html-rendering after span-insertion below\r\nconst tokens = results.toString().replaceAll('<', '<').split('******');\r\n\r\n// build templates from tokens with filepath header and search results in
\r\nconst templates = [];\r\nconst files: string[] = [];\r\nfor (let i = 0; i < tokens.length - 1; i += 2) {\r\n files.push(`${tokens[i + 1]}`);\r\n\r\n // mark substrings in red.\r\n templates.push(`
${tokens[i + 1]}
\r\n
${tokens[i + 2]}
`\r\n .replaceAll(`${substring}`,\r\n `${substring}`)\r\n );\r\n}\r\n\r\n// show the templates => user can scroll in results and then press to continue to dialog for opening project file in IDE\r\nawait div({\r\n html: templates.join('
\\n'),\r\n width: 1200,\r\n height: 700\r\n}, `bg-white text-black text-sm p-2`);\r\n\r\n// put search string in clipboard for use in IDE later\r\nawait copy(substring);\r\n\r\n//---- display buttons in widgets, that let you open IntelliJ Idea -----\r\nconst items = files.map(path => ({\r\n name: path,\r\n // display only shrinked filepath /../ for brevity\r\n display: path.slice(0, path.indexOf('/') + 1) + '..' + path.slice(path.lastIndexOf('/'), path.length)\r\n}));\r\nitems.push({ name: CLOSE_LABEL, display: CLOSE_LABEL });\r\n\r\nconst buttons = `\r\n
\r\n \r\n \r\n
\r\n `;\r\n\r\nlet w = await widget(buttons, {\r\n backgroundColor: '#CCCCAA',\r\n x: 600,\r\n y: Math.max(0, 500 - items.length * 25),\r\n width: 600,\r\n height: items.length * 50 + 50,\r\n state: {\r\n items,\r\n }\r\n});\r\n\r\nw.onClick(async event => {\r\n if (event.dataset.name) {\r\n const path: string = event.dataset.name;\r\n\r\n if (path === CLOSE_LABEL) {\r\n w.close();\r\n exit(0); // process keeps running without..\r\n } else {\r\n // open the project in IntelliJ IDEA\r\n await $`idea ${PROJECT_ROOT}/${path.slice(0, path.indexOf('/'))}`;\r\n // open the specific file chosen inside this project\r\n await $`idea ${PROJECT_ROOT}/${path}`;\r\n // inside IDEA (!) do a search Cmd-F for the substring \r\n // Cmd-V places the substring from Clipboard (where we copied it above) into Ideas Search dialog\r\n await hide();\r\n await keyboard.pressKey(Key.LeftSuper, Key.F);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.F);\r\n await keyboard.pressKey(Key.LeftSuper, Key.V);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.V);\r\n }\r\n }\r\n});```","value":"https://github.com/johnlindquist/kit/discussions/1110","img":"https://avatars.githubusercontent.com/u/99090177?u=23294c71201154e9389c53cc961811acb9aff635&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4","user":"mmustra","author":"Marin Muštra","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1097","url":"https://github.com/johnlindquist/kit/discussions/1097","title":"Normalize GIT branch name","name":"Normalize GIT branch name","extension":".md","description":"Created by mmustra","resourcePath":"/johnlindquist/kit/discussions/1097","createdAt":"2023-02-03T16:21:17Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASZBd","body":"\r\n[Open normalize-git-branch-name in Script Kit](https://scriptkit.com/api/new?name=normalize-git-branch-name&url=https://gist.githubusercontent.com/mmustra/73168d0f629f8b2f526fa45d5068ac12/raw/416c129718d1f9641f6212ba530ab7d6b1f92c06/normalize-git-branch-name.js\")\r\n\r\n```js\r\n// Name: Normalize GIT branch name\r\n// Description: Copy text and paste it to normalized GIT branch name\r\n// Author: Marin Muštra\r\n// LinkedIn: https://www.linkedin.com/in/marin-mustra\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst delimiterChar = '-';\r\nconst illegalChar = '';\r\nconst mergableChars = [delimiterChar, illegalChar];\r\nconst shouldLowerCase = true;\r\n\r\nconst input = await paste();\r\nlet branchName = '';\r\n\r\n// Sanitize references\r\n// https://github.com/microsoft/vscode/blob/main/extensions/git/src/commands.ts#L2182\r\n// https://github.com/gitextensions/gitextensions/blob/6eab7392839c4d103bad1581fba5eaf6f008d766/GitCommands/Git/GitBranchNameNormaliser.cs\r\nconst getSanitizedInput = () => {\r\n const edgePattern = /^[-\\s]+|[-\\s]+$/g;\r\n const delimiterPattern = /\\s+|_+|-+/g;\r\n const illegalPattern = /^-+|^\\.|\\/\\.|\\.\\.|~|\\^|:|\\/$|\\.lock$|\\.lock\\/|\\\\|\\*|\\?|@{|^@$|\\.$|\\[|\\]$|^\\/|\\/$/g;\r\n\r\n const isInvalidChar =\r\n (!edgePattern.test(delimiterChar) && illegalPattern.test(delimiterChar)) ||\r\n (!edgePattern.test(illegalChar) && illegalPattern.test(illegalChar));\r\n\r\n if (isInvalidChar) {\r\n throw new Error('Invalid delimiter/illegal character!');\r\n }\r\n\r\n let sanitized = input.trim().replace(delimiterPattern, delimiterChar).replace(illegalPattern, illegalChar);\r\n mergableChars?.forEach((char) => char && (sanitized = sanitized.replace(new RegExp(`\\\\${char}+`, 'g'), char)));\r\n sanitized = sanitized.replace(edgePattern, '');\r\n\r\n return shouldLowerCase ? sanitized.toLowerCase() : sanitized;\r\n};\r\n\r\ntry {\r\n branchName = getSanitizedInput();\r\n\r\n if (!branchName) {\r\n throw new Error('Invalid input!');\r\n }\r\n} catch (error) {\r\n const hint = `${error.message}`;\r\n await editor({ hint, input, description: 'ERROR', readOnly: true, lineNumbers: 'on' });\r\n\r\n exit();\r\n}\r\n\r\nawait setSelectedText(branchName);\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1097","img":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1094","url":"https://github.com/johnlindquist/kit/discussions/1094","title":"Silent Mention","name":"Silent Mention","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1094","createdAt":"2023-02-02T23:54:13Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASYbe","body":"Described here: https://twitter.com/peduarte/status/1621187086802980866?s=20&t=a8WakFD64i8W3BPDLIzfWg\r\n\r\n\r\n[Open silent-mention in Script Kit](https://scriptkit.com/api/new?name=silent-mention&url=https://gist.githubusercontent.com/johnlindquist/9e4885f4e3d80aa0e66f47a727f206c4/raw/7892b042f75e78ba434cf9e7671a5fa275a17350/silent-mention.ts\")\r\n\r\n```js\r\n// Name: Silent Mention\r\n// Shortcut: opt x\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet makeSilent = (str: string) =>\r\n str.replace(/[.#@]/g, m => m + \"\\u2060\")\r\n\r\nlet text =\r\n (await getSelectedText()) ||\r\n (await arg(\"Enter text to silent\"))\r\n\r\nlet silentText = makeSilent(text)\r\nawait setSelectedText(silentText)\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1094","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1093","url":"https://github.com/johnlindquist/kit/discussions/1093","title":"Script Kit v1.48.0 - February 2023 Release","name":"Script Kit v1.48.0 - February 2023 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1093","createdAt":"2023-02-02T21:32:51Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ASYXj","body":"# Script Kit v1.48.0 - February 2023 Release\r\n\r\nDownload Script Kit v1.48.0 from: https://scriptkit.com\r\n\r\nWatch release video:\r\nhttps://www.youtube.com/watch?v=FQg8AL539_M\r\n\r\n## Drag and Drop Widgets\r\n\r\n![CleanShot 2023-02-02 at 14 31 33](https://user-images.githubusercontent.com/36073/216454341-e1aa117f-c238-4a6b-83af-075c463773ab.gif)\r\n\r\n\r\nWidgets now support `onDrop` and `onMouseDown` handlers. `onMouseDown` can be used in conjunction with `startDrag` to create drag and drop widgets as seen below.\r\n\r\nYou can put any sort of logic in the `onDrop` such as processing files using `ffmpeg`, uploading files to a server, etc, etc.\r\n\r\nThe widget `setState` can be used to show files/progress/etc in effect creating tiny application crafted _just for you_ 🤩\r\n\r\n```js\r\nlet files = []\r\n\r\nlet w = await widget(\r\n `
\r\n
Drop Files
\r\n{{file}}\r\n
\r\n`,\r\n {\r\n containerClass: `p-4 h-screen w-screen overflow-auto`,\r\n width: 300,\r\n height: 300,\r\n draggable: false,\r\n state: {\r\n files,\r\n },\r\n }\r\n)\r\n\r\nw.onDrop(event => {\r\n if (event?.dataset?.files) {\r\n files.push(...event.dataset.files)\r\n w.setState({\r\n files,\r\n })\r\n }\r\n})\r\n\r\nw.onMouseDown(event => {\r\n if (event.dataset.file) {\r\n startDrag(event.dataset.file)\r\n }\r\n})\r\n```\r\n\r\n## `// Alias` Metadata\r\n\r\nHave a script you run often? Use `// Alias` to jump that script to the top of the list when that alias is hit:\r\n\r\n```js\r\n// Alias: hi\r\nsay(\"hi\")\r\n```\r\n\r\n## Fixes for Windows/Linux Installs\r\n\r\nAfter a huge influx of users since the beginning of the year on a variety of different systems and setups, we've been able to test and fix most of the issues that have popped up around various setups with a huge focus on the installation process for Script Kit on Windows and Linux.\r\n\r\nIf you're finding that you're having issues installing Script Kit, please see this discussion: https://github.com/johnlindquist/kit/discussions/1052\r\n","value":"https://github.com/johnlindquist/kit/discussions/1093","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1092","url":"https://github.com/johnlindquist/kit/discussions/1092","title":"Screenshot Current Tweet","name":"Screenshot Current Tweet","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1092","createdAt":"2023-02-02T16:19:30Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASYJl","body":"Focus on a tweet like this:\r\n\r\nhttps://twitter.com/film_girl/status/1621170813796851719\r\n\r\n![1621170813796851719](https://user-images.githubusercontent.com/36073/216381005-74c32641-89e3-416e-af7b-77be619c9c66.png)\r\n\r\nThen run this script for a screenshot:\r\n\r\n\r\n[Open screenshot-current-tweet in Script Kit](https://scriptkit.com/api/new?name=screenshot-current-tweet&url=https://gist.githubusercontent.com/johnlindquist/ced2147f9e918c075e058da4b4c3eb2b/raw/6e46ea8b136c6a0300c69d893b808bb5ad6e80e8/screenshot-current-tweet.ts\")\r\n\r\n```js\r\n// Name: Screenshot Current Tweet\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\nlet url = await getActiveTab()\r\nlet timeout = 5000\r\nlet headless = false\r\n\r\nconst browser = await chromium.launch({\r\n timeout,\r\n headless,\r\n})\r\n\r\nconst context = await browser.newContext({\r\n colorScheme: \"dark\",\r\n})\r\nconst page = await context.newPage()\r\npage.setDefaultTimeout(timeout)\r\n\r\nawait page.goto(url)\r\n\r\nlet screenshotPath = home(\r\n \"Downloads\",\r\n path.parse(url).name + \".png\"\r\n)\r\n\r\ntry {\r\n await page\r\n .locator(\"article[tabindex='-1']\")\r\n .screenshot({ path: screenshotPath })\r\n await revealFile(screenshotPath)\r\n log(`Done`)\r\n} catch (error) {\r\n log(error)\r\n}\r\n\r\nawait browser.close()\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1092","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1088","url":"https://github.com/johnlindquist/kit/discussions/1088","title":"Sleep on Shortcode Example (similar to using an \"alias\")","name":"Sleep on Shortcode Example (similar to using an \"alias\")","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1088","createdAt":"2023-02-01T22:39:21Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXgX","body":"To run the script quickly, from the main prompt type:\r\n\r\ns, then l, then space, then y to confirm.\r\n\r\nSo these four characters:\r\n```\r\nsl y\r\n```\r\n\r\nUsing the `//Shortcode: ` metadata will run the script when you hit the \"spacebar\" after a shortcode/alias.\r\n\r\n\r\n[Open sleep-on-shortcode in Script Kit](https://scriptkit.com/api/new?name=sleep-on-shortcode&url=https://gist.githubusercontent.com/johnlindquist/35e843a00efebb5cceeeb65ea45eb779/raw/e83dd71468ff034c9d5f288387457438d1492c38/sleep-on-shortcode.ts\")\r\n\r\n```js\r\n// Name: Sleep on Shortcode\r\n// Shortcode: sl\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet confirm = await arg({\r\n placeholder: `Sleep system?`,\r\n// Script Kit parses hints and assigns single key shortcuts to single letters inside of []\r\n hint: `[y]/[n]`,\r\n})\r\n\r\nif (confirm === \"y\") {\r\n sleep()\r\n}\r\n\r\n```\r\n\r\n\r\n> Sidenote: From the main menu, you can also type `-` to bring up system commands.","value":"https://github.com/johnlindquist/kit/discussions/1088","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1087","url":"https://github.com/johnlindquist/kit/discussions/1087","title":"Color Picker with saved themes","name":"Color Picker with saved themes","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1087","createdAt":"2023-02-01T21:31:53Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXeJ","body":"\r\n[Open theme-color-picker in Script Kit](https://scriptkit.com/api/new?name=theme-color-picker&url=https://gist.githubusercontent.com/Ambushfall/990b39e9515ac138da5d5c4a5b783f47/raw/19af7566bd72f0e69763b729ee0857a3a1c18130/theme-color-picker.js\")\r\n\r\n```js\r\n// Name: Theme Maker\r\n// Description: Test Themes\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n\r\nconst themePath = kenvPath('theme.json');\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\nconst list = [\"foreground\", \"accent\", \"ui\", \"background\"]\r\nconst valueList = [foreground, accent, ui, background, opacity]\r\n\r\n\r\nconst arrayList = list.map((value, index) => {\r\n return { type: \"color\", label: value, value: valueList[index] }\r\n})\r\n\r\narrayList.push({ type: \"range\", label: \"Opacity\", value: opacity })\r\n\r\nawait fields({\r\n onChange: (input, { value }) => {\r\n const [foreground, accent, ui, background, opacity] = value\r\n theme.foreground = foreground\r\n theme.accent = accent\r\n theme.ui = ui\r\n theme.background = background\r\n theme.opacity = opacity\r\n\r\n setTheme(theme);\r\n writeJson(themePath, theme)\r\n },\r\n fields: arrayList,\r\n})\r\n\r\nlet result = await div(md(`# Success!`))\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1087","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1086","url":"https://github.com/johnlindquist/kit/discussions/1086","title":"Widget Color Picker","name":"Widget Color Picker","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1086","createdAt":"2023-02-01T21:17:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXdp","body":"# Customize your own theme with the color picker\r\n#### Note: Heavily influenced by johnlindquist\r\n\r\n[Open widget-theme in Script Kit](https://scriptkit.com/api/new?name=widget-theme&url=https://gist.githubusercontent.com/Ambushfall/f985c74005580f816a9eaf27852d5902/raw/57c482fec59b1d7ebacde8a2a42010f957af1249/widget-theme.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Name: Widget Theme Picker\r\n// Description: Color Picker HTML\r\n\r\nlet themePath = kenvPath(\"theme.json\")\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\n\r\nlet w = await widget(\r\n `\r\n
`,\r\n {\r\n width: 300,\r\n height: 300,\r\n draggable: false,\r\n }\r\n)\r\n\r\nw.onInput(event => {\r\n setTheme({\r\n [event.dataset.label]: event.value,\r\n })\r\n theme[event.dataset.label] = event.value\r\n writeJson(themePath, theme)\r\n})\r\n\r\nsetIgnoreBlur(true)\r\nawait mainScript()\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1086","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1085","url":"https://github.com/johnlindquist/kit/discussions/1085","title":"Theme Creator Prototype","name":"Theme Creator Prototype","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1085","createdAt":"2023-02-01T19:09:49Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXXu","body":"\r\n[Open theme-creator in Script Kit](https://scriptkit.com/api/new?name=theme-creator&url=https://gist.githubusercontent.com/johnlindquist/72c592fb12ecb45cac31735e572f5516/raw/827416e4c3d92d84144fe2e763b4e3ca3ed849e3/theme-creator.ts\")\r\n\r\n```js\r\n// Name: Theme Creator\r\n\r\n// This will create a file at ~/.kenv/theme.txt\r\n// Edit the file, then hit save to update the theme\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet themePath = kenvPath(\"theme.txt\")\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `\r\n--color-primary: 255, 155, 255\r\n--color-secondary: 255, 113, 39\r\n--color-background: 255, 255, 255\r\n `.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nawait edit(themePath)\r\n\r\nlet { watch } = await npm(\"chokidar\")\r\n\r\nsetIgnoreBlur(true)\r\nlet mS = mainScript()\r\n\r\nwatch(themePath).on(\"change\", async () => {\r\n let contents = await readFile(themePath, \"utf-8\")\r\n\r\n let theme = contents.split(\"\\n\").reduce((acc, line) => {\r\n let [k, v] = line.trim().split(\":\")\r\n acc[k.trim()] = v.trim()\r\n return acc\r\n }, {})\r\n\r\n setTheme(theme)\r\n})\r\n\r\nawait mS\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1085","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1083","url":"https://github.com/johnlindquist/kit/discussions/1083","title":"Copy MacOS version to the clipboard","name":"Copy MacOS version to the clipboard","extension":".md","description":"Created by danielo515","resourcePath":"/johnlindquist/kit/discussions/1083","createdAt":"2023-02-01T09:21:49Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASW69","body":"Lots of support tickets asks me this information that is tedious to get\r\n\r\n[Open os-version in Script Kit](https://scriptkit.com/api/new?name=os-version&url=https://gist.githubusercontent.com/danielo515/244ef0a7557ec42218f9e194f8c85a98/raw/3ec404f3ee8600074b57081a3d4700f2431f2b4f/os-version.js\")\r\n\r\n```js\r\n// Name: os version\r\n\r\nimport \"@johnlindquist/kit\"\r\nconst version_info = (await $`sw_vers`).stdout\r\nconst version_lines = version_info.split(\"\\n\");\r\nconst version_number = version_lines[1].replace(/(?:.*?)(\\d+)/,\"$1\");\r\ncopy(`macOS ${version_number}`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1083","img":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1082","url":"https://github.com/johnlindquist/kit/discussions/1082","title":"Theme Maker - Test","name":"Theme Maker - Test","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1082","createdAt":"2023-02-01T05:12:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASWx3","body":"### For all those that would like to test customizing\r\n\r\nNote: This preview will most likely stop working at some point\r\n\r\n\r\n\r\n[Open theme-color-picker in Script Kit](https://scriptkit.com/api/new?name=theme-color-picker&url=https://gist.githubusercontent.com/Ambushfall/6fb945ea689530704ec081c48e2bf382/raw/a1174df6d04fafcb430b9c981a3aec55a56746d4/theme-color-picker.js\")\r\n\r\n```js\r\n// Name: Theme Maker\r\n// Description: Test Themes\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst setArgOptions = (key, val) => {\r\n return {\r\n name: key, type: \"color\", input: val, onInput: async input => {\r\n const scriptObj = {}\r\n scriptObj[key] = input\r\n setScriptTheme(scriptObj)\r\n }\r\n }\r\n}\r\n\r\n\r\nlet foreground = setArgOptions(\"foreground\", \"#ffffff\")\r\nlet accent = await arg(setArgOptions(\"accent\", \"#fbbf24\"))\r\nlet ui = await arg(setArgOptions(\"ui\", \"#343434\"))\r\nlet background = await arg(setArgOptions(\"background\", \"#000000\"));\r\nlet opacity = await arg({\r\n type: \"range\",\r\n name: \"opacity\",\r\n input: \"85\",\r\n onInput: async input => setScriptTheme({opacity:`0.${input}`})\r\n})\r\n\r\n\r\nlet result = await div(md(`# Success!`))\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1082","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/98099231?u=007356cffb73e56b790e3409a06e0b2938102b8d&v=4","user":"orhan-erday","author":"Orhan Erday","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1079","url":"https://github.com/johnlindquist/kit/discussions/1079","title":"Clone a repository","name":"Clone a repository","extension":".md","description":"Created by orhan-erday","resourcePath":"/johnlindquist/kit/discussions/1079","createdAt":"2023-01-31T07:53:16Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASV62","body":"**Important: Please create `~/GithubProjects` folder**\r\n\r\n[Open with ScriptKIT](https://scriptkit.com/api/new?name=search-starred-repos&url=https://gist.github.com/orhan-erday/6951107bf773dafb3217a41d801c5185)\r\n\r\n```\r\n// Name: Clone A Repository\r\n// Author: Orhan Erday\r\n// Twitter: @orhan_erday\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet repo = await arg(\"Enter a repository, i.e: orhanerday/open-ai\")\r\nlet name = repo.split(\"/\")[1]\r\n// inspect(name)\r\n\r\n// create a folder that called ~/GithubProjects\r\nawait term({\r\n //defaults to home dir\r\n cwd: `~/GithubProjects`,\r\n command: `git clone https://github.com/${repo} && cd ${name} && code .`,\r\n // The footer is optional. All terms continue with the same shortcuts\r\n footer: `ctrl+c or cmd+enter to continue`,\r\n })\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1079","img":"https://avatars.githubusercontent.com/u/98099231?u=007356cffb73e56b790e3409a06e0b2938102b8d&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/60001203?u=3066f343125eb5bdec681f74e178dcbede4f3513&v=4","user":"WaelNalouti","author":"Wael Nalouti","twitter":"WaelNalouti","discussion":"https://github.com/johnlindquist/kit/discussions/1077","url":"https://github.com/johnlindquist/kit/discussions/1077","title":"List my Jira issues","name":"List my Jira issues","extension":".md","description":"Created by WaelNalouti","resourcePath":"/johnlindquist/kit/discussions/1077","createdAt":"2023-01-31T04:13:39Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASVzw","body":"\r\n[Open jira-issues in Script Kit](https://scriptkit.com/api/new?name=jira-issues&url=https://gist.githubusercontent.com/WaelNalouti/baec809b02c18d2e2dba098e6b6bdc11/raw/289dfae5a76c9eb7742375805b1f9875f9fab54a/jira-issues.js\")\r\n\r\n```js\r\n/*\r\n# 🏷️ My Jira issues\r\n- Lists the last 10 issues assigned to me\r\n- You can open the issue in jira\r\n- When selecting an issue, displays a widget on the screen that contains the issue data\r\n- default shortcut to run the script: `cmd+option+shift+j`\r\n*/\r\n\r\n// Name: 🏷️ Jira issues\r\n// Description: List issues assigned to me in Jira\r\n// Shortcut: cmd+option+shift+j\r\n// Author: WaelNalouti\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst JIRA_HOST = await env(\r\n \"JIRA_HOST_URL\",\r\n \"jira host url: exemple.atlassian.net\"\r\n);\r\nconst JIRA_USERNAME = await env(\"JIRA_USERNAME\", \"your jira username\");\r\nconst AUTH = await env(\"AUTH\", \"your jira acces token\");\r\n\r\nconst JIRA_APP_URL = `${JIRA_HOST}/browse`;\r\nconst JIRA_API_URL = `${JIRA_HOST}/rest/api/latest`;\r\n\r\nconst JIRA_ISSUES_QUERY = `/search?orderBy=-created&jql=assignee=%22${JIRA_USERNAME.replaceAll(\r\n \" \",\r\n \"%20\"\r\n)}%22&maxResults=10&startAt=0`;\r\n\r\nlet headersList = {\r\n Authorization: AUTH,\r\n};\r\n\r\nlet response = await get(`${JIRA_API_URL}${JIRA_ISSUES_QUERY}`, {\r\n headers: headersList,\r\n});\r\n\r\nlet data = await response.data;\r\n\r\nlet issuesData = data.issues.map((issue) => ({\r\n key: issue.key,\r\n summary: issue.fields.summary,\r\n issuetype: issue.fields.issuetype,\r\n project: issue.fields.project,\r\n created: issue.fields.created,\r\n priority: issue.fields.priority,\r\n description: issue.fields.description,\r\n creator: issue.fields.creator,\r\n status: issue.fields.status,\r\n}));\r\n\r\nconst selectedIssue = await arg(\"Select an issue to open in browser\", () => {\r\n return issuesData.map((issue) => ({\r\n name: issue.summary,\r\n icon: issue.issuetype.iconUrl,\r\n description: `[${issue.status.statusCategory.name}] - ${issue.project.name}/${issue.key}`,\r\n value: issue.key,\r\n preview: () =>\r\n `\r\n
${\r\n issue.description ||\r\n \"No description for this task 🌵\"\r\n }
\r\n
\r\n `,\r\n {\r\n alwaysOnTop: true,\r\n opacity: 0.7,\r\n roundedCorners: true,\r\n hasShadow: true,\r\n draggable: true,\r\n backgroundColor: \"#000\",\r\n // frame: true,\r\n darkTheme: true,\r\n useContentSize: true,\r\n }\r\n);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1077","img":"https://avatars.githubusercontent.com/u/60001203?u=3066f343125eb5bdec681f74e178dcbede4f3513&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1074","url":"https://github.com/johnlindquist/kit/discussions/1074","title":"Win32 - bat/ps1/sh execution / editor or hardcoded","name":"Win32 - bat/ps1/sh execution / editor or hardcoded","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1074","createdAt":"2023-01-29T22:05:52Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASUt0","body":"\r\n[Open win32-run-script in Script Kit](https://scriptkit.com/api/new?name=win32-run-script&url=https://gist.githubusercontent.com/Ambushfall/a3c0d71372f2739ef37494c66036aa4c/raw/3e4ae12c01db9c584bd77db660739f8cc0d8ec3a/win32-run-script.ts\")\r\n\r\n```js\r\n// Name: Run .bat/.ps1/.sh\r\n// Description: Process Output to Kit via stream\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n// @ts-expect-error\r\nimport { backToMainShortcut, highlightJavaScript } from \"@johnlindquist/kit\"\r\n\r\n// --- Create a shell script to run -----------------\r\n// `tmpPath` will store the file here:\r\n// ~/.kenv/tmp/process-shell-script-output/example.*\r\n\r\n// Note: linux shell will only work with WSL or you can provide the Args for ps1 using the .sh extension if you have gitbash\r\n\r\nconst fileName = \"example\"\r\n\r\nconst selectedLang = {\r\n name: '',\r\n args: '',\r\n ext: '',\r\n echo: '',\r\n\r\n set setVal(keyValueList: string[]) {\r\n this[keyValueList[0]] = keyValueList[1];\r\n }\r\n};\r\n\r\nconst objGen = (_lang: string, _ext: string, _args?: string) => {\r\n _args = _args ? _args : \"\"\r\n return {\r\n name: _lang,\r\n description: `Run Script using ${_lang}`,\r\n value: _lang,\r\n id: _ext,\r\n arguments: _args,\r\n preview: async () => highlightJavaScript(tmpPath(`${fileName}.${_ext}`))\r\n }\r\n}\r\n\r\nconst LangOptions = [\r\n objGen(\"PowerShell\",\r\n \"ps1\",\r\n \"powershell -NoProfile -NonInteractive –ExecutionPolicy Bypass -File \"\r\n ), objGen(\"Batch\", \"bat\"),\r\n objGen(\"Bash\", \"sh\"\r\n )]\r\n\r\nconst promptEditor = [\"yes\", \"no\"]\r\n\r\nconst selectedValue = await arg(\"Use editor?\", promptEditor\r\n)\r\n\r\nconst useEditor = selectedValue === \"yes\" ? true : false\r\n\r\n// define select options\r\n\r\nawait arg({\r\n placeholder: \"Select Scripting Language...\",\r\n enter: \"Select\",\r\n shortcuts: [backToMainShortcut],\r\n onChoiceFocus: async (input, { focused }) => {\r\n selectedLang.setVal = [\"args\", focused[\"arguments\"]]\r\n selectedLang.setVal = [\"ext\", focused.id]\r\n selectedLang.setVal = [\"name\", focused.name]\r\n selectedLang.setVal = [\"echo\", selectedLang.ext == \"bat\" ? \"@echo off\" : \"\"]\r\n }\r\n}, LangOptions)\r\n\r\n\r\n\r\nlet shellScriptPath = tmpPath(`${fileName}.${selectedLang.ext}`)\r\n\r\nconst editorConfig = {\r\n hint: `Write code for ${selectedLang.ext} file.`,\r\n description: 'Save to Run',\r\n onInputSubmit: async (input: any) => {\r\n selectedLang.ext == \"sh\" ? submit(`${input}\r\n exit`) : submit(input)\r\n }\r\n}\r\n\r\n// Using ping to simulate waiting for a long process and because it's natively supported across PS and Bat files\r\n// Note: If you use a code that would natively not run in bat like \"ls\" it will \r\nlet scriptContents = useEditor ? await editor(editorConfig) : `${selectedLang.echo}\r\necho \"hello\"\r\necho \"Done\"\r\n${selectedLang.ext == \"sh\" ? \"exit\" : \"\"}\r\n`\r\n\r\nawait writeFile(shellScriptPath, scriptContents);\r\n\r\n// Just a wrapper to highlight with code in PS style\r\nconst codeWrapper = (string: string, extension: any) => `\r\n\\`\\`\\`${extension}\r\n${string}\r\n\\`\\`\\`\r\n`\r\n\r\nlet output = ``\r\n\r\n// This is used to avoid kit window closing on process exit\r\nlet divPromise = div()\r\n\r\nconst outHandler = async (out: any) => {\r\n output += `${out}\\n`;\r\n setDiv(\r\n await highlight(`${codeWrapper(output, selectedLang.ext)}`)\r\n )\r\n};\r\n\r\n// Note: We have to use this janky way of executing PS as it would launch in Notepad or fail entirely.\r\nconst execArgs = selectedLang.ext == \"sh\" ? `cd ${tmpPath()} && bash ${fileName}.sh` : `${selectedLang.args}${shellScriptPath}`\r\n\r\n// inspect(execArgs)\r\nlet { stdout } = execLog(execArgs, outHandler)\r\n\r\nsetAlwaysOnTop(true);\r\nsetIgnoreBlur(true);\r\n\r\nawait divPromise\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1074","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1071","url":"https://github.com/johnlindquist/kit/discussions/1071","title":"Write or Paste JSON > TS-Interfaces","name":"Write or Paste JSON > TS-Interfaces","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1071","createdAt":"2023-01-28T13:45:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASUAQ","body":"This script is highly inspired off of already present 2 scripts:\r\n\r\n[share-script-as-discussion-js](https://github.com/johnlindquist/kit/blob/main/src/cli/share-script-as-discussion.ts)\r\n[JSON to TypeScript](https://github.com/johnlindquist/kit/discussions/1068)\r\n\r\n[Open json-to-ts in Script Kit](https://scriptkit.com/api/new?name=json-to-ts&url=https://gist.githubusercontent.com/Ambushfall/242170cfbd1f17f2c3749ef5f30964d5/raw/62afba807d7aa3ac21f5e2951b81d447dccddb3d/json-to-ts.ts\")\r\n\r\n```js\r\n// Name: JSON to TS-Interfaces\r\n// Description: Quickly get an Interface from JSON\r\n// Author: Ambushfall\r\n// Snippet: json..ts\r\n// Inspired by: Marin Muštra's JSON to TypeScript\r\n\r\n\r\nimport '@johnlindquist/kit';\r\nimport { EditorConfig } from '@johnlindquist/kit/types/kitapp';\r\n\r\nconst JsonToTS = await npm('json-to-ts');\r\n\r\nconst TSWrapper = (string: string) => `\r\n\\`\\`\\`ts\r\n${string}\r\n\\`\\`\\`\r\n`\r\n\r\nconst JSONWrapper = (string: string) => `\r\n\\`\\`\\`json\r\n${string}\r\n\\`\\`\\`\r\n`\r\n\r\nconst editorConfig:EditorConfig = {\r\n hint: \"Write then submit to obtain results\",\r\n description: 'JSON Values to Interface',\r\n onInputSubmit: async (input: string) => submit(input)\r\n}\r\n\r\n// Initialize editor and Parse JSON\r\nconst json = await editor(editorConfig)\r\nconst obj = JSON.parse(json);\r\nconst types = `${JsonToTS(obj).join('\\n\\n')}\\n`;\r\n\r\n// Place text into ClipBoard and show results\r\nsetSelectedText(types)\r\nawait div(await highlight(`# Success! Result Copied to Clipboard!\r\n## Input:${JSONWrapper(json)} \r\n## Output:${TSWrapper(types)}`));\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1071","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/11914327?u=a0ce83d03c79ce1eea9a73880258bf80b02a0065&v=4","user":"ryanbaer","author":"Ryan","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1070","url":"https://github.com/johnlindquist/kit/discussions/1070","title":"🧙♂️ Resolve Short Links","name":"🧙♂️ Resolve Short Links","extension":".md","description":"Created by ryanbaer","resourcePath":"/johnlindquist/kit/discussions/1070","createdAt":"2023-01-28T06:39:31Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AST2_","body":"[Open resolve-short-links in Script Kit](https://scriptkit.com/api/new?name=resolve-short-links&url=https://gist.githubusercontent.com/ryanbaer/51d55aa0562b75d400d8e993ee3e8e8c/raw/4f1728cbbb36dd806c84a686d14dfc63493dc079/resolve-short-links.ts)\r\n\r\n# 🧙♂️ Resolve Short Links\r\n\r\n## Description\r\n\r\n- ⌨️ Prompts the user for a URL\r\n- 🌏 Makes a lightweight request to retrieve the actual URL\r\n- 🧼 Sanitizes unnecessary tracking information\r\n- 📋 Automatically copies the resolved URL to the clipboard.\r\n\r\n## Explanation\r\n\r\nThis is useful for removing tracking / other information from URLs before sharing with others.\r\n\r\nMany short links embed user / tracking information in the URL on their end.\r\n\r\nEvery time you share this shortened URL, your information is passed along.\r\n\r\n- TikTok, for example, uses this to display your username on the page when others view the video you shared.\r\n\r\n- Additionally, many short links will append query parameters that include tracking information as well, such as Facebook's `fbclid`.\r\n\r\nThis script resolves the short link by sending a `HEAD` request to the URL.\r\n\r\n- Additionally, any unnecessary query parameters are stripped off\r\n\r\n- The result is a valid URL with the minimum amount of information needed to access the resource\r\n\r\n## Examples\r\n\r\n|Site|Input|Resolved & Sanitized|\r\n|-|-|-|\r\n|Bitly|https://bit.ly/3IBRlRk?fbclid=csGcfbGQWeVOwKtolljwoSVhwcxZsnDungMTiiycUaTyBkgGlErwsLXEpaVOX|https://www.insider.com/private-caribbean-island-for-sale-photos-2022-8|\r\n|TikTok|https://www.tiktok.com/t/kZnATxcP/|https://www.tiktok.com/@the_tim_davidson/video/7148742306085063978|\r\n|Facebook Watch|https://fb.watch/ZLce_Hif0_/|https://www.facebook.com/watch/?v=752603792999748|\r\n\r\n```ts\r\n// Name: 🧙♂️ Resolve Short Links\r\n// Description: Resolve short links & removes any tracking info\r\n// Author: Ryan Baer\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst Resolver = {\r\n requiredParams: {\r\n // youtube.com/watch?v=[videoId] requires the 'v' param to locate the video\r\n \"youtube.com\": [\"v\"],\r\n // facebook.com/watch?v=[videoId] requires the 'v' param to locate the video\r\n \"facebook.com\": [\"v\"],\r\n },\r\n getURL(input: string) {\r\n let processed = input;\r\n if (!input.match(/^https?:\\/\\//)) {\r\n processed = `https://${processed}`;\r\n }\r\n\r\n return new URL(processed);\r\n },\r\n\r\n sanitizeUrl(url: URL) {\r\n function getBaseDomain(hostname: string) {\r\n const [tld, base] = hostname.split(\".\").reverse();\r\n\r\n return `${base}.${tld}`;\r\n }\r\n\r\n // Clone so we're not deleting from the list as we're iterating it.\r\n const searchParams = new URLSearchParams(url.searchParams);\r\n\r\n searchParams.forEach((_value, key) => {\r\n const baseDomain = getBaseDomain(url.hostname);\r\n const skip = this.requiredParams[baseDomain];\r\n if (skip?.includes(key)) {\r\n return;\r\n }\r\n\r\n url.searchParams.delete(key);\r\n });\r\n\r\n return url.href;\r\n },\r\n\r\n async fetchHead(input: string) {\r\n let url: URL;\r\n try {\r\n url = this.getURL(input);\r\n } catch (error) {\r\n return { resolvedUrl: undefined, error: \"Invaid or unreachable URL\" };\r\n }\r\n\r\n const options = { method: \"HEAD\", credentials: \"omit\" } as const;\r\n\r\n try {\r\n const response = await fetch(url, options);\r\n const responseUrl = new URL(response.url);\r\n const sanitizedUrl = this.sanitizeUrl(responseUrl);\r\n\r\n return { resolvedUrl: sanitizedUrl, error: undefined };\r\n } catch (error) {\r\n return { resolvedUrl: undefined, error: this.getErrorMessage(error) };\r\n }\r\n },\r\n\r\n getErrorMessage(error: unknown) {\r\n return error instanceof Error\r\n ? error.message\r\n : \"Something unexpected happened.\";\r\n },\r\n\r\n async resolve(url: string) {\r\n return this.fetchHead(url);\r\n },\r\n};\r\n\r\nconst url = await arg(\"Enter a shortened URL\");\r\nconst response = await Resolver.resolve(url);\r\n\r\nconst { resolvedUrl, error } = response;\r\n\r\nconst result = error ?? resolvedUrl ?? \"(Something went wrong)\";\r\n\r\nconst shouldCopy = !!resolvedUrl;\r\nif (shouldCopy) {\r\n await copy(result);\r\n}\r\n\r\nawait div(\r\n md(`# 🔬 Result${shouldCopy ? \" (automatically copied to clipboard)\" : \"\"}\r\n \\`\\`\\`\r\n ${result}\r\n \\`\\`\\`\r\n `)\r\n);\r\n","value":"https://github.com/johnlindquist/kit/discussions/1070","img":"https://avatars.githubusercontent.com/u/11914327?u=a0ce83d03c79ce1eea9a73880258bf80b02a0065&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/11914327?u=a0ce83d03c79ce1eea9a73880258bf80b02a0065&v=4","user":"ryanbaer","author":"Ryan","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1069","url":"https://github.com/johnlindquist/kit/discussions/1069","title":"🗡️ Strip Query Params","name":"🗡️ Strip Query Params","extension":".md","description":"Created by ryanbaer","resourcePath":"/johnlindquist/kit/discussions/1069","createdAt":"2023-01-28T04:29:13Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AST0-","body":"[Open strip-query-params in Script Kit](https://scriptkit.com/api/new?name=strip-query-params&url=https://gist.githubusercontent.com/ryanbaer/ca7bcf557d3c81a4d13d1c15060f49ff/raw/988f965857eceef7f8588bd78d80c3501b6974e2/strip-query-params.ts)\r\n\r\n# 🗡️ Strip Query Params\r\n\r\n- ⌨️ Prompts the user for a URL\r\n- 🗡️ Cuts off all query params\r\n- 🛡️ Maintains essential query params (e.g., `youtube.com/watch?v=[videoId]`)\r\n- 📋 Automatically copies the updated URL to the clipboard.\r\n\r\nUseful for removing tracking / other information from URLs before sharing with others.\r\n\r\n## Examples\r\n\r\n|Site|Input|Output|\r\n|-|-|-|\r\n|Facebook Video|https://www.facebook.com/watch/?v=1233056997105202&extid=NS-UNK-CHE-UNK-IOS_WXYZ-ABCD&mibextid=cfeeni&ref=sharing|https://www.facebook.com/watch/?v=1233056997105202|\r\n|Facebook|https://www.facebook.com/groups/funnycatsworld/permalink/5619404908185458/?mibextid=cfeeni|https://www.facebook.com/groups/funnycatsworld/permalink/5619404908185458/|\r\n|Instagram|https://www.instagram.com/reel/CnxZnkRprAD/?igshid=YmNmMTdmZDM=|https://www.instagram.com/reel/CnxZnkRprAD/|\r\n|YouTube|https://www.youtube.com/watch?v=MWRPYBoCEaY&ab_channel=NoBoilerplate|https://www.youtube.com/watch?v=MWRPYBoCEaY|\r\n\r\n```ts\r\n// Name: 🗡️ Strip Query Params\r\n// Description: Strips query params from a URL.\r\n// Author: Ryan Baer\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n// TODO: let's leverage the awesome `db` feature and allow users to add their own\r\n// rules.\r\nconst requiredParams: Record = {\r\n // youtube.com/watch?v=[videoId] requires the 'v' param to locate the video\r\n \"youtube.com\": [\"v\"],\r\n // facebook.com/watch/?v=[videoId] requires the 'v' param to locate the video\r\n \"facebook.com\": [\"v\"],\r\n};\r\n\r\nfunction stripQueryParams(input: string) {\r\n const url = new URL(input);\r\n\r\n function getBaseDomain(hostname: string) {\r\n const [tld, base] = hostname.split(\".\").reverse();\r\n\r\n return `${base}.${tld}`;\r\n }\r\n\r\n // Clone so we're not deleting from the list as we're iterating it.\r\n const searchParams = new URLSearchParams(url.searchParams);\r\n\r\n searchParams.forEach((_value, key) => {\r\n const baseDomain = getBaseDomain(url.hostname);\r\n const skip = requiredParams[baseDomain];\r\n if (skip?.includes(key)) {\r\n return;\r\n }\r\n\r\n url.searchParams.delete(key);\r\n });\r\n\r\n return url.href;\r\n}\r\n\r\nconst input = await arg(\"Enter a URL\");\r\nconst result = stripQueryParams(input);\r\n\r\nawait copy(result);\r\n\r\nawait div(\r\n md(`# 🔬 Result (automatically copied to clipboard)\r\n\\`\\`\\`\r\n${result}\r\n\\`\\`\\`\r\n`)\r\n);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1069","img":"https://avatars.githubusercontent.com/u/11914327?u=a0ce83d03c79ce1eea9a73880258bf80b02a0065&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4","user":"mmustra","author":"Marin Muštra","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1068","url":"https://github.com/johnlindquist/kit/discussions/1068","title":"JSON to TypeScript","name":"JSON to TypeScript","extension":".md","description":"Created by mmustra","resourcePath":"/johnlindquist/kit/discussions/1068","createdAt":"2023-01-28T01:14:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASTxi","body":"\r\n[Open json-to-typescript in Script Kit](https://scriptkit.com/api/new?name=json-to-typescript&url=https://gist.githubusercontent.com/mmustra/c544b9ae06baea89b95257f752f453d4/raw/f80cde38dc04857b558b5bc35023b8c92131e7f2/json-to-typescript.js\")\r\n\r\n```js\r\n// Name: JSON to TypeScript\r\n// Description: Copy JSON and paste it to TypeScript interfaces\r\n// Author: Marin Muštra\r\n// LinkedIn: https://www.linkedin.com/in/marin-mustra\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst JsonToTS = await npm('json-to-ts');\r\nconst json = await paste();\r\nlet types = '';\r\n\r\ntry {\r\n const obj = JSON.parse(json);\r\n types = `${JsonToTS(obj).join('\\n\\n')}\\n`;\r\n} catch (error) {\r\n const hint = `${error.message}`;\r\n await editor({ hint, input: json, description: 'ERROR', readOnly: true, lineNumbers: 'on' });\r\n\r\n exit();\r\n}\r\n\r\nawait setSelectedText(types);\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1068","img":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1067","url":"https://github.com/johnlindquist/kit/discussions/1067","title":"Win32 - Working Project Opener / Editor","name":"Win32 - Working Project Opener / Editor","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1067","createdAt":"2023-01-27T20:23:48Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASTox","body":"\r\n[Open dev-project in Script Kit](https://scriptkit.com/api/new?name=dev-project&url=https://gist.githubusercontent.com/Ambushfall/6bf540b63a5b4fc942d65deaec04f149/raw/5f0d048c608dc35558c8b661cb6c9c455c69efcb/dev-project.js\")\r\n\r\n```js\r\n// Preview: docs\r\n// Menu: Open Project\r\n// Description: Opens a project in vscode\r\n// Shortcut: cmd shift .\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst envPath = await env('PROJECT_DIR');\r\n\r\nconst projectDir = home(envPath);\r\n\r\nconst projects = await readdir(projectDir);\r\n\r\nconst filteredProjects = projects.filter((v) => v.includes('.') ? false : v)\r\n\r\nconst project = await arg('Enter a project name:', filteredProjects);\r\n\r\nconst projectPath = path.resolve(projectDir, project);\r\n\r\nedit(projectPath)\r\n```\r\n\r\n# Todos\r\n- [ ] Make it so editor does not create an untitled file\r\n","value":"https://github.com/johnlindquist/kit/discussions/1067","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/39631861?u=ecdb890a3c471983cfaf94b596c0e57d9149f670&v=4","user":"gammaSpeck","author":"Madhu KM","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1062","url":"https://github.com/johnlindquist/kit/discussions/1062","title":"Ask a single query with CHAT GPT","name":"Ask a single query with CHAT GPT","extension":".md","description":"Created by gammaSpeck","resourcePath":"/johnlindquist/kit/discussions/1062","createdAt":"2023-01-27T11:27:42Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASTSE","body":"\r\n![Screen Recording 2023-01-27 at 11 20 43 PM](https://user-images.githubusercontent.com/39631861/215159017-6eef65a8-27d7-4203-8a45-e0dec4d8feea.gif)\r\n\r\n\r\n[Open chat-gpt-simple in Script Kit](https://scriptkit.com/api/new?name=chat-gpt-simple&url=https://gist.githubusercontent.com/gammaSpeck/1470334d43185b67f009bf32fdc25d23/raw/905260c7c4892d981d78e24de251ac41fbd8ca0c/chat-gpt-simple.ts)\r\n\r\n\r\n\r\n---\r\n\r\n## Prequisites:\r\n1. Get an API KEY from https://beta.openai.com/account/api-keys. \r\n2. Paste that API key inside your `.kenv/.env` file as `OPENAI_API_KEY=`\r\n3. Running the script will work as expected.\r\n\r\n\r\n## Script snippet\r\n\r\n```js\r\n// Name: Chat with CHAT GPT\r\n// Description: Ask CHAT GPT Anything\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { Configuration, OpenAIApi } = await npm(\"openai\");\r\n// Use below line for better TS hints\r\n// import { Configuration, OpenAIApi } from \"openai\";\r\n\r\nconst configuration = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\")\r\n});\r\n\r\nconst openAI = new OpenAIApi(configuration);\r\n\r\nconst prompt = await editor(\"\");\r\n\r\nconst completion = await openAI.createCompletion({\r\n model: \"text-davinci-003\",\r\n prompt,\r\n temperature: 0,\r\n max_tokens: 4069\r\n});\r\n\r\nconst choices = completion.data.choices;\r\n\r\nconst response = choices[0].text;\r\n// Uncomment if you want your computer to speak the response\r\n// say(response);\r\n\r\nconst containerClassName =\r\n \"flex justify-center items-center text-2xl h-full p-10\";\r\n\r\nawait div(response, containerClassName);\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1062","img":"https://avatars.githubusercontent.com/u/39631861?u=ecdb890a3c471983cfaf94b596c0e57d9149f670&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/98099231?u=007356cffb73e56b790e3409a06e0b2938102b8d&v=4","user":"orhan-erday","author":"Orhan Erday","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1055","url":"https://github.com/johnlindquist/kit/discussions/1055","title":"JIRA JQL Query Script: Easily Search and View Issues in Your JIRA Instance","name":"JIRA JQL Query Script: Easily Search and View Issues in Your JIRA Instance","extension":".md","description":"Created by orhan-erday","resourcePath":"/johnlindquist/kit/discussions/1055","createdAt":"2023-01-25T13:23:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASRwe","body":"This script allows the user to run a JIRA JQL (Jira Query Language) query and display the results. The script prompts the user to enter a JQL query, or it uses a default query that searches for all issues assigned to the current user that are not closed or marked as \"Done\" or \"Development Done\", and orders the results by the date they were created. The script then uses the JIRA API to perform the search and display the results in a list format. The user can select one of the issues from the list and the script will open the selected issue in the browser. The script requires the user to set the `JIRA_HOST`, `JIRA_USERNAME`, and `JIRA_PWD` environment variables to connect to their JIRA instance\r\n\r\n[Open with ScriptKIT](https://scriptkit.com/api/new?name=search-starred-repos&url=https://gist.github.com/orhan-erday/6582bb1b4a60f06dcb8ba4831b6f2c96) \r\n\r\n````javascript\r\n\r\n// Name: JIRA\r\n// Description: Run JQL query\r\n// Author: Orhan Erday\r\n// Twitter: @orhan_erday\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet jql = await arg(\"Enter the JQL, or press enter if you want to get Your work.\")\r\n\r\nif (jql == \"\") {\r\n jql = `assignee=currentUser() and status != Closed and status != \"Done\" and status != \"Development Done\" order by created DESC`\r\n}\r\n\r\nlet jira = {\r\n host: await env(\"JIRA_HOST\", \"example.atlassian.net\"),\r\n jql: jql \r\n}\r\n\r\nlet response = await get(`https://${jira.host}/rest/api/2/search?jql=${jira.jql}`, {\r\n auth: {\r\n username: await env(\"JIRA_USERNAME\"),\r\n password: await env(\"JIRA_PWD\")\r\n }\r\n})\r\n\r\n// inspect(response.data.issues)\r\n\r\nlet key = await arg(\r\n `Select JIRA:`,\r\n response.data.issues.map(({\r\n fields,\r\n key\r\n }) => {\r\n return {\r\n name: key,\r\n description: fields.summary,\r\n value: key,\r\n preview: async () => {\r\n\r\n\r\n return md(\r\n `\r\n# ${fields.summary}\r\n\r\n## Description \r\n\r\n${fields.description} \r\n\r\n## Issue Type\r\n\r\n${fields.parent.fields.status.name} \r\n\r\n## Status\r\n\r\n${fields.parent.fields.issuetype.name} `)\r\n },\r\n }\r\n })\r\n)\r\n\r\nawait $`start https://${jira.host}/browse/${key}`\r\n````","value":"https://github.com/johnlindquist/kit/discussions/1055","img":"https://avatars.githubusercontent.com/u/98099231?u=007356cffb73e56b790e3409a06e0b2938102b8d&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/79809121?u=94c6b55ab061ac71d15afbceb0542582be7dec35&v=4","user":"takanome-dev","author":"El Hadji Malick Seck","twitter":"takanome_dev","discussion":"https://github.com/johnlindquist/kit/discussions/1051","url":"https://github.com/johnlindquist/kit/discussions/1051","title":"Browse, preview and open Github notifications","name":"Browse, preview and open Github notifications","extension":".md","description":"Created by takanome-dev","resourcePath":"/johnlindquist/kit/discussions/1051","createdAt":"2023-01-24T23:48:10Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASRSk","body":"\r\n[Open check-github-notifications in Script Kit](https://scriptkit.com/api/new?name=check-github-notifications&url=https://gist.githubusercontent.com/TAKANOME-DEV/d516ef95bf67c2cfd5e09bc057dd7487/raw/f60143ad1365b295eaac3ca5138e38e1504bc45e/check-github-notifications.ts\")\r\n\r\nI created this script to quickly browse through my Github notifications, preview them and jump on them if needed without leaving my vscode or other programs. It's heavenly inspired by @bastibuck ([Search and open starred GitHub repos script](https://github.com/johnlindquist/kit/discussions/1044))\r\n\r\n![brandbird-jpg-1](https://user-images.githubusercontent.com/79809121/214446206-232c80c9-e42c-44a6-9382-2675edbd45d0.jpg)\r\n\r\n\r\n```js\r\n// Name: Github Notifications\r\n// Description: Browse, preview and open your GitHub notifications\r\n// Author: TAKANOME DEV\r\n// Twitter: @takanome_dev\r\n// Github: @TAKANOME-DEV\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { Octokit }: typeof import(\"octokit\") = await npm(\"octokit\")\r\nimport { Endpoints } from \"@octokit/types\"\r\n\r\ntype Notification = Endpoints[\"GET /notifications\"][\"response\"][\"data\"][number]\r\ntype IssueComment = Endpoints[\"GET /repos/{owner}/{repo}/issues/{issue_number}/comments\"][\"response\"][\"data\"][number]\r\n\r\n\r\nconst prIcon = `\r\n`\r\nconst chatIcon = `\r\n`\r\nconst commitIcon = `\r\n`\r\nconst openIssuePrIcon = `\r\n`\r\nconst releaseIcon = `\r\n`\r\n\r\nconst AUTH_TOKEN = await env(\"GH_CLASSIC_TOKEN\", {\r\n hint: md(`\r\n Create a [personal access token](https://github.com/settings/tokens) with the \\`notifications\\` scope.\r\n `),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst octokit = new Octokit({\r\n auth: AUTH_TOKEN,\r\n})\r\n\r\nconst formattedDate = (date: string) => {\r\n const d = new Date(date)\r\n return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`\r\n}\r\n\r\nconst renderIcon = (type: string) => {\r\n switch (type) {\r\n case 'PullRequest':\r\n return prIcon\r\n case 'Issue':\r\n return openIssuePrIcon\r\n case 'Commit':\r\n return commitIcon\r\n case 'Discussion':\r\n return chatIcon\r\n case 'Release':\r\n return releaseIcon\r\n default:\r\n return '' \r\n }\r\n}\r\n\r\nconst authorAssociationEmoji = (authorAssociation: string) => {\r\n switch (authorAssociation) {\r\n case 'OWNER':\r\n return '👑'\r\n case 'COLLABORATOR':\r\n return '👨👩👧👦'\r\n case 'CONTRIBUTOR':\r\n return '👨💻'\r\n case 'FIRST_TIMER':\r\n return '👶'\r\n case 'FIRST_TIME_CONTRIBUTOR':\r\n return '👶'\r\n case 'MANNEQUIN':\r\n return '🤖'\r\n case 'MEMBER':\r\n return '👪'\r\n default:\r\n return ''\r\n }\r\n}\r\n\r\nconst getUrl = (notification: Notification): string => {\r\n switch (notification.subject.type) {\r\n case \"Discussion\":\r\n return `${notification.repository.html_url}/discussions`\r\n case \"Release\":\r\n return `${notification.repository.html_url}/releases/tag/${notification.subject.title}`\r\n case \"Issue\":\r\n return `${notification.repository.html_url}/issues/${notification.subject.url?.split('/').pop()}`\r\n case \"PullRequest\":\r\n return `${notification.repository.html_url}/pull/${notification.subject.url?.split('/').pop()}`\r\n }\r\n}\r\n\r\nconst notificationReason = await arg(\r\n 'Choose a notification reason:',\r\n [\r\n { name: '🛎 All', value: 'all', description: 'All notifications' },\r\n { name: '🤹 Assign', value: 'assign', description: 'You were assigned to the thread' },\r\n { name: '👨💻 Author', value: 'author', description: 'You created the thread' },\r\n { name: '💬 Comment', value: 'comment', description: 'You commented on the thread' },\r\n { name: '📨 Invitation', value: 'invitation', description: 'You were invited to a repository' },\r\n { name: '👨🏫 Manual', value: 'manual', description: 'You are watching the repository' },\r\n { name: '👋 Mention', value: 'mention', description: 'You were @mentioned in the thread' },\r\n { name: '👀 Review Requested', value: 'review_requested', description: 'You were requested to review a pull request' },\r\n { name: '🚨 Security Alert', value: 'security_alert', description: 'A security alert was published' },\r\n { name: '🔁 State Change', value: 'state_change', description: 'The thread was marked as read' },\r\n { name: '👍 Subscribed', value: 'subscribed', description: 'You are subscribed to the thread' },\r\n { name: '👨👩👧👦 Team Mention', value: 'team_mention', description: 'You were @mentioned in the thread' },\r\n ])\r\n\r\nconst notifications = await octokit.paginate(\"GET /notifications\")\r\n\r\nconst notifs = await arg(\r\n \"Search through notifications...\",\r\n notifications.filter((n) => {\r\n if (notificationReason === 'all') {\r\n return true\r\n }\r\n return n.reason === notificationReason\r\n }).map((n) => {\r\n\r\n return {\r\n // TODO: render icon in front of name\r\n name: n.repository.full_name,\r\n value: getUrl(n),\r\n description: `${n.last_read_at ? '' : '🔔'} ${n.subject.title} - _${formattedDate(n.updated_at)}_`,\r\n icon: n.repository.owner.avatar_url,\r\n preview: async () => {\r\n const response = await octokit.paginate(\"Get /repos/{owner}/{repo}/issues/{issue_number}/comments\", {\r\n owner: n.repository.owner.login,\r\n repo: n.repository.name,\r\n issue_number: n.subject.url.split('/').pop(),\r\n })\r\n\r\n const comments = response.map((comment) => {\r\n return `## @${comment.user.login} - ${formattedDate(comment.updated_at)} ${comment.updated_at === comment.created_at ? '' : '🔁'} ${comment.author_association ? `- ${comment.author_association}` : ''} ${authorAssociationEmoji(comment.author_association)} \\n\\n ${comment.body}`\r\n }).join('\\n\\n')\r\n\r\n return md(`
${renderIcon(n.subject.type)} ${n.subject.title}
\\n\\n ${comments}`)\r\n }\r\n };\r\n })\r\n);\r\n\r\nbrowse(notifs);\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1051","img":"https://avatars.githubusercontent.com/u/79809121?u=94c6b55ab061ac71d15afbceb0542582be7dec35&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/8796757?u=cb54a30aceeb3c98faaafd302dbcd5187d7eb40f&v=4","user":"Cauen","author":"Emanuel","twitter":"cauenor","discussion":"https://github.com/johnlindquist/kit/discussions/1046","url":"https://github.com/johnlindquist/kit/discussions/1046","title":"Deep URL Decode","name":"Deep URL Decode","extension":".md","description":"Created by Cauen","resourcePath":"/johnlindquist/kit/discussions/1046","createdAt":"2023-01-23T16:10:18Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASQNw","body":"Parses complex links like this\r\n```\r\nhttps://google.com/notify/messages/1295812905/click?signature=10295801298586296300235&url=https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\r\n```\r\n\r\nInto:\r\n\r\n```json\r\n{\r\n \"1: URL Original\": \"https://google.com/notify/messages/1295812905/click?signature=10295801298586296300235&url=https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\",\r\n \"2: URL Decoded\": \"https://google.com/notify/messages/1295812905/click?signature=10295801298586296300235&url=https://google.com/users/link/ZW1hbnVlbC5-9AF80a98fg8vbQ==MTY3NDQzMjg2NA==6a8f74a552?redirect_to=https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795&utm_source=escalation_mailer&utm_medium=email&utm_campaign=resolved\",\r\n \"3: URL Params\": {\r\n \"signature\": \"10295801298586296300235\",\r\n \"url\": \"https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\"\r\n },\r\n \"4: Children\": {\r\n \"url\": {\r\n \"1: URL Original\": \"https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\",\r\n \"2: URL Decoded\": \"https://google.com/users/link/ZW1hbnVlbC5-9AF80a98fg8vbQ==MTY3NDQzMjg2NA==6a8f74a552?redirect_to=https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795&utm_source=escalation_mailer&utm_medium=email&utm_campaign=resolved\",\r\n \"3: URL Params\": {\r\n \"redirect_to\": \"https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795\",\r\n \"utm_source\": \"escalation_mailer\",\r\n \"utm_medium\": \"email\",\r\n \"utm_campaign\": \"resolved\"\r\n },\r\n \"4: Children\": {\r\n \"redirect_to\": {\r\n \"1: URL Original\": \"https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795\",\r\n \"2: URL Decoded\": \"https://google.com/team/832678/incidents/331734795\",\r\n \"3: URL Params\": {},\r\n \"4: Children\": {}\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\n\r\n[Install](https://scriptkit.com/api/new?name=deep-url-decode&url=https://gist.githubusercontent.com/Cauen/80db9eee7ce5c04969e73ef46159c18d/raw/6c6f88772e9d254744ccd003512af299e486f8fc/deep-url-decode.ts)\r\n\r\n```ts\r\n// Name: Deep URL decode\r\n// Description: Sometimes you want to know what inside a URL, use this to show as a deep json...\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst decode = (str: string) => decodeURIComponent((str + '').replace(/\\+/g, '%20'))\r\n\r\nfunction getParamsFromUrl(url: string): Record | void {\r\n if (url.startsWith(\"https%\")) return getParamsFromUrl(decode(url))\r\n\r\n try {\r\n if (typeof url === 'string') {\r\n let params = url.split('?');\r\n let eachParamsArr = params[1].split('&');\r\n let obj: Record = {};\r\n if (eachParamsArr && eachParamsArr.length) {\r\n eachParamsArr.map(param => {\r\n let keyValuePair = param.split('=')\r\n let key = keyValuePair[0];\r\n let value = keyValuePair[1];\r\n obj[key] = value;\r\n })\r\n }\r\n return obj;\r\n }\r\n } catch (err) {\r\n // return getParamsFromUrl(decode(url))\r\n return {}\r\n }\r\n}\r\n\r\ninterface DecodeType {\r\n ['1: URL Original']: string\r\n ['2: URL Decoded']: string\r\n ['3: URL Params']: void | Record\r\n ['4: Children']: Record\r\n}\r\nfunction decodeDeep(urlString: string): DecodeType {\r\n const decodedUrl = decode(urlString)\r\n const paramsFromUrl = getParamsFromUrl(urlString)\r\n\r\n return {\r\n ['1: URL Original']: urlString,\r\n ['2: URL Decoded']: decodedUrl,\r\n ['3: URL Params']: paramsFromUrl,\r\n ['4: Children']: (() => {\r\n if (!paramsFromUrl) return {}\r\n const entries = Object.entries(paramsFromUrl)\r\n return entries.reduce((current, [paramKey, paramValue]) => {\r\n const itemIsUrl = paramValue.startsWith(\"http\")\r\n if (!itemIsUrl) return current\r\n const decoded = decodeDeep(paramValue)\r\n if (!decoded) return current\r\n // return current\r\n return { ...current, [paramKey]: decoded }\r\n }, {})\r\n })()\r\n }\r\n}\r\n\r\nlet url = await arg(\"enter URL\")\r\n\r\ninspect(decodeDeep(url))\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1046","img":"https://avatars.githubusercontent.com/u/8796757?u=cb54a30aceeb3c98faaafd302dbcd5187d7eb40f&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/6306291?u=248fb2425c610afbf4805bbfe0b9529104bbeef8&v=4","user":"bastibuck","author":"Basti Buck","twitter":"bastibuck","discussion":"https://github.com/johnlindquist/kit/discussions/1044","url":"https://github.com/johnlindquist/kit/discussions/1044","title":"Search and open starred GitHub repos","name":"Search and open starred GitHub repos","extension":".md","description":"Created by bastibuck","resourcePath":"/johnlindquist/kit/discussions/1044","createdAt":"2023-01-23T13:40:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASQDr","body":"\r\n[Open search-starred-repos in Script Kit](https://scriptkit.com/api/new?name=search-starred-repos&url=https://gist.githubusercontent.com/bastibuck/44f37ed22722aac25f8eb363d06a4160/raw/8cbdba2058829a2be31d948c795f0abbacefaae2/search-starred-repos.ts)\r\n\r\nThis script allows searching and opening your (or anyone's) starred repositories from GitHub. It works by setting up a second, scheduled script that downloads and caches the repos in `db()` so searching is quick every time afterwards. \r\n\r\nWhen running this script for the first time it checks if the cache script exists and otherwise creates it for you with the given schedule. On first run it doesn't have any cached repos yet and therefor tells you to run the cache-script manually once.\r\n\r\nOn consecutive runs the search script uses cached starred repositories to display choices and opens the selection in the browser.\r\n\r\n\r\n```ts\r\n// Name: Search starred GitHub repos\r\n// Description: Search and filter starred GitHub repositories\r\n// Author: Basti Buck\r\n// Twitter: @bastibuck\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport { Choices } from \"@johnlindquist/kit\";\r\n\r\nconst CACHE_SCRIPT_NAME = \"cache-starred-repos.ts\";\r\n\r\nconst { isValidCron }: typeof import(\"cron-validator\") = await npm(\r\n \"cron-validator\"\r\n);\r\n\r\n// setup caching script if not installed\r\nconst installedScripts = await readdir(home(kenvPath(\"/scripts\")));\r\n\r\nif (!installedScripts.includes(CACHE_SCRIPT_NAME)) {\r\n const cronSchedule = await arg({\r\n placeholder:\r\n \"Choose schedule or define your own schedule using CRON syntax\",\r\n strict: false,\r\n choices: getCronChoices,\r\n validate: validateCRON,\r\n });\r\n\r\n await appendFile(\r\n home(kenvPath(\"/scripts\"), CACHE_SCRIPT_NAME),\r\n buildCacheScript({ cronSchedule })\r\n );\r\n\r\n await div(\r\n `
\r\n
\r\n Looks like this is your first run.\r\n
\r\n\r\n
I've setup a caching script for you.
\r\n
\r\n This script will run on the defined schedule and cache your starred repos for faster searching. If you need to run the caching manually you can do so from Kit.\r\n
\r\n\r\n
\r\n To initialize your cache now, run Cache starred GitHub repos.\r\n
\r\n
`\r\n );\r\n\r\n exit(0);\r\n}\r\n\r\n// show filterable starred repos\r\nlet { cachedStars } = await db(\"starred-repos\", { cachedStars: [] });\r\n\r\nconst star = await arg(\r\n \"Search starred repos...\",\r\n cachedStars.map((star) => {\r\n return {\r\n name: star.full_name,\r\n value: star.html_url,\r\n description: `⭐ ${star.stargazers_count} ${\r\n star.description ? \" | \" + star.description : \"\"\r\n }`,\r\n icon: star.owner.avatar_url,\r\n };\r\n })\r\n);\r\n\r\nbrowse(star);\r\n\r\n// helper functions\r\n\r\nfunction getCronChoices(): Choices {\r\n return [\r\n {\r\n name: \"0 12 * * *\",\r\n value: \"0 12 * * *\",\r\n description: \"Daily at noon\",\r\n },\r\n {\r\n name: \"0 8 * * *\",\r\n value: \"0 8 * * *\",\r\n description: \"Daily at 8:00 am\",\r\n },\r\n {\r\n name: \"0 8 * * 1\",\r\n value: \"0 8 * * 1\",\r\n description: \"Weekly on Monday morning\",\r\n },\r\n ];\r\n}\r\n\r\nfunction validateCRON(cron: string) {\r\n return isValidCron(cron)\r\n ? true\r\n : \"Invalid CRON syntax. Check crontab.guru for help\";\r\n}\r\n\r\nfunction buildCacheScript({ cronSchedule }: { cronSchedule: string }) {\r\n return `// Name: Cache starred GitHub repos\r\n// Description: Cache starred GitHub repositories for future operations\r\n// Author: Basti Buck\r\n// Twitter: @bastibuck\r\n\\/\\/ Schedule: ${cronSchedule}\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { z }: typeof import(\"zod\") = await npm(\"zod\");\r\nconst { Octokit }: typeof import(\"octokit\") = await npm(\"octokit\");\r\nimport { Endpoints } from \"@octokit/types\";\r\n\r\nconst AUTH_TOKEN = await env(\"GITHUB_AUTH_TOKEN\", {\r\n hint: md(\r\n \\`Create a [personal access token](https://github.com/settings/tokens) with the \\\\\\`read:user\\\\\\` scope.\\`\r\n ),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst USERNAME = await env(\"GITHUB_USERNAME\", {\r\n hint: md(\\`Your GitHub username\\`),\r\n});\r\n\r\ntype Star = Endpoints[\"GET /user/starred\"][\"response\"][\"data\"][number];\r\n\r\nconst cachedStarSchema = z.array(\r\n z.object({\r\n full_name: z.string(),\r\n html_url: z.string(),\r\n stargazers_count: z.number(),\r\n description: z.string().optional().nullish(),\r\n owner: z.object({\r\n avatar_url: z.string(),\r\n }),\r\n })\r\n);\r\n\r\nconst octokit = new Octokit({\r\n auth: AUTH_TOKEN,\r\n});\r\n\r\nlet { cachedStars, write } = await db(\"starred-repos\", {\r\n cachedStars: [],\r\n});\r\n\r\nconst stars = await octokit.paginate(\\`GET /users/\\${USERNAME}/starred\\`);\r\n\r\n_.remove(cachedStars, () => true);\r\n\r\ncachedStars.push(...cachedStarSchema.parse(stars));\r\n\r\nawait write();\r\n`;\r\n}\r\n\r\n\r\n```\r\n\r\nAny feedback appreciated 😀 \r\n","value":"https://github.com/johnlindquist/kit/discussions/1044","img":"https://avatars.githubusercontent.com/u/6306291?u=248fb2425c610afbf4805bbfe0b9529104bbeef8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13529535?u=38ecbbdc3611ee1cb873b67ab948558e141d3748&v=4","user":"lukethacoder","author":"Luke Secomb","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1042","url":"https://github.com/johnlindquist/kit/discussions/1042","title":"sPoNgEcAsE / aLt CaPS","name":"sPoNgEcAsE / aLt CaPS","extension":".md","description":"Created by lukethacoder","resourcePath":"/johnlindquist/kit/discussions/1042","createdAt":"2023-01-23T09:22:58Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASP5w","body":"A small silly script to convert inputted text to sPoNgEcAsE (or aLt CaPS). Saves to your clipboard ready to paste wherever you like.\r\n\r\n```typescript\r\n// Name: sPoNgEcAsE Text\r\n// Description: Converts input text to sPoNgEcAsE (or aLt CaPS)\r\n// Author: Luke Secomb\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction sPoNgEbObCaSe(inputText: string) {\r\n return inputText\r\n .split('')\r\n .map((char, index) => (index % 2 ? char.toUpperCase() : char.toLowerCase()))\r\n .join('');\r\n}\r\n\r\nconst userInput = await arg('Enter text');\r\ncopy(sPoNgEbObCaSe(userInput));\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1042","img":"https://avatars.githubusercontent.com/u/13529535?u=38ecbbdc3611ee1cb873b67ab948558e141d3748&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/78121870?u=247cb0fe42c53610056202437f09154f357fa306&v=4","user":"youngkyo0504","author":"kyoyoung keum","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1041","url":"https://github.com/johnlindquist/kit/discussions/1041","title":"open chrome bookmark (show all nested bookmark)","name":"open chrome bookmark (show all nested bookmark)","extension":".md","description":"Created by youngkyo0504","resourcePath":"/johnlindquist/kit/discussions/1041","createdAt":"2023-01-23T01:56:17Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASPvL","body":"This displays all of your bookmarks. (including nested bookmark)\r\n\r\n[Open open chrome bookmark in Script Kit](https://scriptkit.com/api/new?name=open-chrome-bookmark&url=https://gist.githubusercontent.com/youngkyo0504/eb5d1e73abe685672f8868d949b98831/raw/38b73f53d1194458418bf7e7d9feb5fa0b64b9fb/open-crhome-bookmark.js)\r\n\r\n\r\n```javascript\r\nimport \"@johnlindquist/kit\";\r\n\r\n// Menu: Open Chrome Bookmark\r\n// Description: List Chrome Bookmarks. Then open tab.\r\n// Author: kyo young\r\n// GitHub: @youngkyo0504\r\n\r\nlet rawBookmarks = await readFile(\r\n home(\"Library/Application Support/Google/Chrome/Default/Bookmarks\"),\r\n \"utf8\"\r\n);\r\n\r\nconst parsedBookmarks = JSON.parse(rawBookmarks);\r\nconst bookmarkStructure = parsedBookmarks.roots.bookmark_bar.children;\r\n\r\nconst bookmarks = (function flatten(\r\n bookmarkElements\r\n) {\r\n return bookmarkElements.reduce((acc, cur) => {\r\n if (cur.type === \"folder\") {\r\n return [...acc, ...flatten(cur.children)];\r\n }\r\n\r\n return [...acc, cur];\r\n }, []);\r\n})(bookmarkStructure);\r\n\r\nlet bookmarkChoices = bookmarks.map(({ name, url }) => {\r\n return {\r\n name: name,\r\n description: url,\r\n value: url,\r\n };\r\n});\r\n\r\nlet bookmarksAndOpen = [...bookmarkChoices];\r\nlet choices = _.uniqBy(bookmarksAndOpen, \"name\");\r\n\r\nlet url = await arg(\"Oepn Chrome tab:\", choices);\r\n\r\nfocusTab(url);\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1041","img":"https://avatars.githubusercontent.com/u/78121870?u=247cb0fe42c53610056202437f09154f357fa306&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/78121870?u=247cb0fe42c53610056202437f09154f357fa306&v=4","user":"youngkyo0504","author":"kyoyoung keum","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1038","url":"https://github.com/johnlindquist/kit/discussions/1038","title":"port kill","name":"port kill","extension":".md","description":"Created by youngkyo0504","resourcePath":"/johnlindquist/kit/discussions/1038","createdAt":"2023-01-22T11:50:57Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASPYp","body":"[Open Port kill in Script Kit](https://scriptkit.com/api/new?name=time-from-now&url=https://gist.githubusercontent.com/youngkyo0504/f8632d4775b7964188dc714538c81991/raw/b38e4402c31c320d427077feff73aba65f60a190/port-kill.js\r\n)\r\n\r\n|Success|Fail|\r\n|---|---|\r\n|||\r\n\r\n\r\n```js\r\n// Name: Port kill\r\n// Description: Enter port number to kill process listening on port.\r\n// Author: kyo young\r\n// GitHub: @youngkyo0504\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst killPort = await npm('kill-port');\r\nconst port = await arg('Enter port to kill')\r\nconst containerClassName = 'flex justify-center items-center text-4xl h-full'\r\n\r\ntry {\r\n await killPort(port, 'tcp')\r\n await div(`🤖 listening on port ${port} has been killed.`,containerClassName);\r\n} catch (error) {\r\n console.error(error);\r\n await div(`\r\n 🛰️ ${error.message}\r\n `,containerClassName)\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1038","img":"https://avatars.githubusercontent.com/u/78121870?u=247cb0fe42c53610056202437f09154f357fa306&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/37197876?u=de8de604e5a8d9a0b70487f3e9d5c89e506d3bbf&v=4","user":"m1yon","author":"Michael Lyon","twitter":"__mlyon","discussion":"https://github.com/johnlindquist/kit/discussions/1024","url":"https://github.com/johnlindquist/kit/discussions/1024","title":"Open GitHub Repository","name":"Open GitHub Repository","extension":".md","description":"Created by m1yon","resourcePath":"/johnlindquist/kit/discussions/1024","createdAt":"2023-01-20T21:35:58Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASOsY","body":"Easily view and open GitHub repositories.\r\n\r\nA simplified version of @vogelino's [\"Browse GitHub repos and perform actions on them\" script](https://github.com/johnlindquist/kit/discussions/893).\r\n\r\n___\r\n\r\n[Open open-github-repository in Script Kit](https://scriptkit.com/api/new?name=open-github-repository&url=https://gist.githubusercontent.com/m1yon/154ce4a6e16fe1540d2b65098687b97b/raw/439ef530fd2275f399019e7e42a0302b293518cb/open-github-repository.ts\")\r\n\r\n```js\r\n// Name: Open GitHub Repository\r\n// Description: Open a specific GitHub repository\r\n// Author: Michael Lyon\r\n// Twitter: @__mlyon\r\n\r\nimport '@johnlindquist/kit'\r\n\r\nconst { Octokit } = await npm('octokit')\r\n\r\ninterface RawRepositoryType {\r\n id: string\r\n name: string\r\n html_url: string\r\n full_name: string\r\n visibility: string[]\r\n description: string\r\n homepage?: string\r\n owner: {\r\n login: string\r\n }\r\n open_issues_count: number\r\n}\r\n\r\ninterface OptionType {\r\n name: string\r\n descritpion?: string\r\n preview?: string\r\n value: ValueType\r\n}\r\n\r\nconst auth = await env(`GITHUB_ACCESS_TOKEN`, 'Enter your GitHub access token')\r\nconst octokit = new Octokit({ auth })\r\n\r\nconst {\r\n data: { login },\r\n} = await octokit.rest.users.getAuthenticated()\r\n\r\nconst mapRawRepo = (repo: RawRepositoryType) => ({\r\n name: repo.full_name,\r\n description: [\r\n repo.visibility[0].toUpperCase() + repo.visibility.slice(1),\r\n repo.description,\r\n repo.homepage,\r\n ]\r\n .filter(Boolean)\r\n .join(' · '),\r\n value: repo,\r\n})\r\n\r\nconst mapReposResponse = (response: { data: RawRepositoryType[] }) =>\r\n (response.data || []).map(mapRawRepo)\r\n\r\nasync function fetchAllRepos() {\r\n return await octokit.paginate(\r\n octokit.rest.repos.listForAuthenticatedUser,\r\n { sort: 'updated', per_page: 100 },\r\n mapReposResponse,\r\n )\r\n}\r\n\r\nasync function fetchRecentRepos() {\r\n const res = await octokit.request('GET /user/repos', {\r\n sort: 'updated',\r\n per_page: 50,\r\n })\r\n return res.data\r\n}\r\n\r\nasync function fetchOwnerRepos() {\r\n const res = await octokit.request('GET /user/repos', {\r\n sort: 'updated',\r\n per_page: 50,\r\n affiliation: 'owner',\r\n })\r\n return res.data\r\n}\r\n\r\nfunction getTabHandler(getter: () => Promise[]>) {\r\n return async function handler() {\r\n const repos = await getter()\r\n const repoSelected = await arg(`Hello ${login}. Search for a repo`, repos)\r\n\r\n if (repos.length === 0) {\r\n await div(`
No repos
`)\r\n await handler()\r\n }\r\n\r\n await browse(repoSelected.html_url)\r\n exit()\r\n }\r\n}\r\n\r\nconst recentTab = getTabHandler(fetchRecentRepos)\r\nonTab('Recent', recentTab)\r\nonTab('Owner', getTabHandler(fetchOwnerRepos))\r\nonTab('All', getTabHandler(fetchAllRepos))\r\nawait recentTab()\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1024","img":"https://avatars.githubusercontent.com/u/37197876?u=de8de604e5a8d9a0b70487f3e9d5c89e506d3bbf&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/970","url":"https://github.com/johnlindquist/kit/discussions/970","title":"Script Kit v1.39.17 December Release","name":"Script Kit v1.39.17 December Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/970","createdAt":"2022-12-08T02:29:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ARsPf","body":"# Script Kit v1.39.17 December Release\r\n\r\n\r\n\r\n\r\nDownload from https://www.scriptkit.com/\r\n\r\n## Kit.app Re-Design\r\n\r\nScript Kit now has a new look. The theming was completely re-written to help build out customs themes, so we took the opportunity to clean up the main theme.\r\n\r\nYou may also notice subtle differences in app shadows, transparency, etc which are thanks to the latest Electron updates supporting \"Panel\" windows (which we were accomplishing through third-party, less-than-ideal ways before). Speaking of updates...\r\n\r\n## Updated to the Latest Versions\r\n\r\nScript Kit is now on the latest Electron 22, node 16.17.1, and many of the internal dependencies how been updated as well. If you're a developer, you know how big of an undertaking this can be (and how refreshing it is when you're done).\r\n\r\nElectron 22 is an exciting release from a dev standpoint and we can't wait to dig into using some of the new features.\r\n\r\n## Alternate Keymap Support\r\n\r\nScript Kit shortcuts will now support your custom/international keymaps. If will also detect if a keymap has changed and remap based on the new configuration\r\n\r\n> Note: If you have a shortcut that accounted for the keymap being \"wrong\", you'll need to update it to the correct version\r\n\r\n## Run a Script From Other Apps with ~/.kit/run.txt\r\n\r\nIf you want to launch Script Kit from the terminal or another app, write the command and arguments to the `~/.kit/run.txt` file. \r\nScript Kit watches for changes and will run the command you write to the file.\r\n\r\nThe following will run the script `browse-scriptkit.js` in your ~/.kenv/scripts dir:\r\n\r\nMac:\r\n```bash\r\necho browse-scriptkit > ~/.kit/run.txt\r\n```\r\n\r\nWindows:\r\n```cmd\r\necho browse-scriptkit > %HOMEPATH%\\.kit\\run.txt\r\n```\r\n\r\n> Note: You can still use ~/.kit/kar, but I wanted to offer an alternative to our Windows friends\r\n\r\n## Watchers Menu\r\n\r\n> 🚨 Note: It's now required to manually start the snippet/clipboard watcher from the menubar icon->Watchers menu.\r\n\r\n Some users reported difficulty/freezing with the keyboard monitoring for snippets due to various other app conflicts, hardware conflicts, etc, so we decided to allow more control over starting/stopping/waking the watcher in case something happens. We worked hard in this release to address these issues, but decided it's still best to give control to the user.\r\n\r\n## Windows setSelectedText() Fixed\r\n\r\nWindows will now properly hide the prompt to be able to paste text to the app behind it\r\n\r\n## Soon... Scripts on GitHub Actions\r\n\r\nMade significant progress towards using Script Kit on GitHub Actions. Need some more time to test and handle edge cases, but we're close!!!","value":"https://github.com/johnlindquist/kit/discussions/970","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/949","url":"https://github.com/johnlindquist/kit/discussions/949","title":"Script Kit v1.36.0 November 2022 Release","name":"Script Kit v1.36.0 November 2022 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/949","createdAt":"2022-11-18T17:39:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AReJw","body":"# Script Kit v1.36.0 November 2022 Release\r\n\r\nDownload from [https://www.scriptkit.com/](https://www.scriptkit.com/)\r\n\r\n\r\n
\r\n \r\n
\r\n\r\n\r\n## Script Markdown\r\n\r\nPlace a multiline comment at the top of your script to display Markdown to the user when previewing the script. For example:\r\n\r\n```js\r\n/*\r\n# Rotate Images\r\n- Get the selected image from Finder\r\n- Creates 3 new versions rotated at 90, 180, and 270\r\n*/\r\n```\r\n\r\n## Script `Enter` Metadata\r\n\r\nAdding `Enter` metadata will now change the text displayed by the \"Run\" button in the script preview. For example:\r\n\r\n```js\r\n// Enter: Rotate Images\r\n```\r\n\r\n## Themes (Pro)\r\n\r\nFrom the \"Kit\" tab, you can now select a theme for the Script Kit UI. There are a variety of themes to choose from and in upcoming releases, you'll be able to create your personalized themes that you can share.\r\n\r\n## Log Window (Pro)\r\n\r\nRun a script with `alt+enter` to open the log window. This window will display the output of your script from and commands you run or any console logs.\r\n\r\n## Debugger (Pro)\r\n\r\nRun a script with `ctrl+enter` to open the debugger. The debugger will automatically pause on any `debugger` statements and allow you to step through your script and inspect/modify variables.\r\n\r\n## Account\r\n\r\nYou can now sign in to GitHub to unlock more features. The first feature is `createGist` (which is used behind the scenes in sharing scripts already) which is now exposed to users:\r\n\r\n```js\r\nlet {url} = await createGist(\"My content\")\r\n```\r\n\r\nIn the future, this account will be used for:\r\n- syncing your scripts with a GitHub repo\r\n- connecting to GitHub repos to run GitHub Actions\r\n- displaying stats about your scripts\r\n- pro plan/team plans\r\n- and much more...\r\n\r\n\r\n## Widgets Dynamic Lists\r\n\r\nWidgets can now use dynamic lists and get data from an item selected. Please check out this example and watch the youtube video for more details.\r\n\r\nhttps://github.com/johnlindquist/kit/discussions/948\r\n\r\n## Snippet Keyboard Layouts\r\n\r\nSome users reported that Snippets were not working on non-standard keyboard layouts. The snippet engine has been updated to detect your current system keyboard layout and adjust accordingly.\r\n\r\n## Focus Window\r\n> Requires \"Security & Privacy\" > \"Accessibility\" > \"Screen Recording\" permission to be enabled to work so it can get the window title names.\r\n\r\nHit colon (:) from the main menu to open the focus window script. This lists all of the windows open and allows you to select which window to bring into focus.\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/949","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/925","url":"https://github.com/johnlindquist/kit/discussions/925","title":"October 2022 Release (v1.32.2) - Windows Preview 🤩, Debugger 🐞, and Repairs 🛠️","name":"October 2022 Release (v1.32.2) - Windows Preview 🤩, Debugger 🐞, and Repairs 🛠️","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/925","createdAt":"2022-10-27T16:08:15Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ARNRI","body":"# Script Kit 1.32.2 Released 🎉\r\n\r\nDownload here: https://scriptkit.com\r\n\r\n## Windows Preview 🤩\r\n\r\nWe are excited to announce the first Windows preview of Script Kit! This build is very close to feature-parity with the OSX version, so you can expect the vast majority of scripts you run on Mac to also run on Windows.\r\n\r\n> Note: We haven't purchased the Windows code signing certificate yet, so Windows will warn you that it can't verify Script Kit when you begin the install process. Also, this causes that Windows updates will need to be installed manually. We plan on setting up the Windows code-signing certificate by the end of the year to fix these issues then we'll remove the \"Preview\" label 😊\r\n\r\nSnippets, Watchers, Schedule, etc, etc, etc all work. If an api is not supported on Windows (mostly the functions that get information about the desktop) then it will display a \"Not supported on Windows\" message.\r\n\r\nThe \"Browse\" (`/` from the main menu) and \"File Search\" (`.` from the main menu) both required a ton of work, but they're working as well. We'll get the App Launcher sorted out in the next release.\r\n\r\n## Debugger\r\n\r\nYou can now debug your scripts by simply pressing `cmd+enter` from the main menu (`ctrl+enter` on Windows). This will open a built-in inspector that will allow you to step through your script, set breakpoints, and inspect variables.\r\n\r\nUse the `debugger` keyword to set a breakpoint in your script. The inspector will pause execution when it hits the breakpoint and you can mess around with the variables and step through the script to your heart's content. You can even invoke functions such as `setDescription()` and treat the inspector like a REPL.\r\n\r\n## Repair\r\n\r\nThe menubar now includes a `Debug` menu. From `Debug->Force Repair Kit SDK` you can force the Kit SDK to be reinstalled. This is useful if you're having issues with the Kit SDK due to npm acting up or if an update failed.\r\n\r\n## `await cutText()`\r\n\r\nThe `cutText()` function will cut the latest typed word and bring it into your script.\r\n\r\n```js\r\nlet word = await cutText()\r\n\r\n// Send word to an API, wrap the word in markdown, etc, etc\r\n```\r\n\r\n## Other Fixes\r\n\r\n* The TypeScript esbuild compiling should be faster and more stable\r\n* New install splash now shows the npm progress status\r\n* App Launcher fixes (`;` from the main menu)\r\n* Google Fixes (`~` from the main menu)","value":"https://github.com/johnlindquist/kit/discussions/925","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/901","url":"https://github.com/johnlindquist/kit/discussions/901","title":"September 2022 Release (version 1.30.8) - Postfix Snippets, UI Shortcuts, VS Code Extension, and Much, much more!","name":"September 2022 Release (version 1.30.8) - Postfix Snippets, UI Shortcuts, VS Code Extension, and Much, much more!","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/901","createdAt":"2022-09-29T08:49:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AQ537","body":"# Script Kit 1.30.8 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\n\r\nThis is our first release after a couple of busy summer months, and it's slam-packed full of new features and UX!\r\n\r\n## Postfix Snippets\r\n\r\nPostfix snippets are snippets that are triggered by typing a postfix after a \"variable\" of text. The variable is represented by a `*` at the beginning of snippet. In the `*html,,` example below, typing `divhtml,,` would treat `div` as the variable and render out `
Hello world
`.\r\n\r\n### Postfix Snippet Hello World\r\n\r\n```js\r\n// Name: Example Postfix Snippet\r\n// Snippet: *html,,\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"I'm the '*' content\")\r\n\r\nsetSelectedText(`<${value}>Hello world${value}>`)\r\n```\r\n\r\n### Postfix Snippet Query API\r\n\r\nThe snippet can also take the content of the `*` and post it to an API for more complex scripts.\r\n\r\nIn this example, the content is used to query google and create a markdown link from the word you typed.\r\n\r\n```js\r\n// Name: Markdown Link from Google Snippet\r\n// Snippet: *,.\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet google = await npm(\"googlethis\")\r\n\r\nconst options = {\r\n page: 0, \r\n safe: false,\r\n additional_params: { \r\n hl: 'en' \r\n }\r\n}\r\n\r\nlet value = await arg(\"I'm the asterisk content\")\r\nlet response = await google.search(value, options);\r\n\r\nlet url = response.results[0].url\r\n\r\nsetSelectedText(`[${value}](${url})`)\r\n```\r\n\r\n\r\n## `template()`\r\n\r\nThe `template` prompt will present the editor populated by your template. You can then tab through each variable in your template and edit it. \r\n\r\nTemplates pair really, _really_ nicely with Snippets!\r\n\r\n### Template Hello World\r\n\r\n```js\r\nlet text = await template(`Hello $1!`)\r\n```\r\n\r\n### Standard Usage\r\n\r\n```js\r\nlet text = await template(`\r\nDear \\${1:name},\r\n\r\nPlease meet me at \\${2:address}\r\n\r\n Sincerely, John`)\r\n```\r\n\r\n\r\n## `await docs()`\r\n\r\n`docs()` takes the file path of a markdown file and displays it as a list of choices.\r\n\r\nEach h2 is displayed as a choice while the content of the h2 is displayed in the preview.\r\n\r\nSelected the choice will return current h2.\r\n\r\n### Submitting a `docs()` value\r\n\r\nIf you'd rather submit a value instead of the h2, then use an HTML comment to specify the value under the h2's content:\r\n\r\n```md\r\n## I'm the Choice Header\r\n\r\n```\r\n\r\n### `docs()` Example\r\n\r\n```js\r\n// Name: Example Docs\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await download(`https://raw.githubusercontent.com/BuilderIO/qwik/main/README.md`)\r\n\r\nlet filePath = tmpPath(\"README.md\")\r\nawait writeFile(filePath, buffer)\r\n\r\n\r\nlet selectedDoc = await docs(filePath)\r\n\r\ndev({selectedDoc})\r\n```\r\n\r\n## UI Shortcuts in the \"Action Bar\"\r\n\r\nThe September 2022 release adds a new \"Action Bar\" at the bottom of the UI which supports custom shortcuts.\r\n\r\nA shortcut has a `name`, `key`, `onPress` and `bar` property. When you press the shortcut, it will trigger the `onPress` function. You can also click on the shortcut to trigger the `onPress` function.\r\n\r\n```js\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n```\r\n\r\n\r\nAdd each shortcuts to a `shortcuts` array which is passed to the prompt config (most commonly, the first argument of `arg()`).\r\n\r\n### UI Shortcuts Example\r\n\r\n```js\r\n// Name: Shortcuts Example\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n\r\nlet reloadChoices = {\r\n name: \"Reload\",\r\n key: \"cmd+l\",\r\n bar: \"right\",\r\n onPress: async () => { \r\n setChoices([\"one\",\"two\",\"three\"])\r\n }\r\n} \r\n\r\nlet submitInputNotChoice = {\r\n name: \"Submit Input\",\r\n key: \"cmd+i\",\r\n bar: \"right\",\r\n onPress: async (input) => { \r\n submit(input)\r\n }\r\n} \r\n\r\nlet runAppLauncher = {\r\n name: \"Launch App\",\r\n key: \"cmd+a\",\r\n bar: \"left\",\r\n onPress: async () => { \r\n await run(kitPath(\"main\", \"app-launcher.js\"))\r\n }\r\n}\r\n\r\nlet result = await arg({\r\n placeholder: \"Shortcut demo\",\r\n shortcuts: [ \r\n clearInput,\r\n reloadChoices,\r\n submitInputNotChoice,\r\n runAppLauncher\r\n ]\r\n}, [\"apple\", \"banana\", \"cherry\"])\r\n\r\nawait div(md(`## ${result}`))\r\n```\r\n\r\n## Recent Script Moved to Top\r\n\r\nThe most recently run script is now moved to the top of the list so that the next time you open the main prompt, you can quickly run it again.\r\n\r\n\r\n## VS Code Extension\r\n\r\nWe now have a VS Code extension which allows you to run scripts directly from VS Code:\r\n\r\nhttps://marketplace.visualstudio.com/items?itemName=johnlindquist.kit-extension\r\n\r\nMore features are coming soon!\r\n\r\n## Menubar Updates\r\n\r\nThe menubar menu now has a \"Dev Tools\" menu which allows you to perform some common commands that might not be obvious from the main UI.\r\n\r\nThe menu also lists all of the running processes so that you can easily terminate them without having to hunt around from process ids.\r\n\r\n## cmd+tab to Widgets and `dev`\r\n\r\nWhen the main prompt is open, a widget is open, or a `dev` window is open, the Script Kit icon will be added to the doc to allow you to cmd+tab back to widgets/editor/dev/etc. Since Script Kit is a combination of a temporary prompt (like Alfred) but also can host long-running widgets, we had to work through various scenarios of when cmd+tab can be available. Hopefully we've landed on a solution that works for everyone.\r\n\r\n## Main Menu `API` and `Guide`\r\n\r\nThe Main Menu of Script Kit now hosts `API` and `Guide` tabs which allow you to easily copy code snippets or create new scripts from the examples. They're also both easy to update, so you can expect more samples and explanations to play with in the future!\r\n\r\n## `await emoji()`\r\n\r\nA brand new emoji picker\r\n\r\n```js\r\nlet e = await emoji()\r\nsetSelectedText(e.emoji)\r\n```\r\n\r\n## `await fields()`\r\n\r\n```js\r\nlet [first, last] = await fields([\"First name\", \"Last name\"])\r\n\r\ndev({\r\n first,\r\n last\r\n})\r\n```\r\n\r\n## `beep()`\r\n\r\nAnd the most exciting announcement of all, `beep()` is now available!\r\n\r\n`beep()` plays a beep sound.\r\n\r\n### `beep()` Example\r\n\r\n```js\r\nbeep()\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/901","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/787","url":"https://github.com/johnlindquist/kit/discussions/787","title":"June 2022 Release (version 1.19.3) - Menu, Native Keyboard, ignoreBlur","name":"June 2022 Release (version 1.19.3) - Menu, Native Keyboard, ignoreBlur","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/787","createdAt":"2022-06-21T16:44:14Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AP4Se","body":"# Script Kit 1.19.3 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\nDirect downloads:\r\n* Intel: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3.dmg\r\n* m1: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3-arm64.dmg\r\n\r\n## Features\r\n\r\n* Moved all notifications to menu colored “dots” (red, orange, green)\r\n * In the future, the user will be able to set these notifications as well\r\n* Completely re-vamped menu for common options\r\n* Implemented an “auth helpers” when user does actions that require “accessibility permissions” (Snippets, clipboard history, setSelectedText, etc)\r\n* `ignoreBlur` allows window to go behind other windows and stay open\r\n* `node` is now stored in `~/.knode` (instead of ~/.kit/node) to allow npx to work in the terminal\r\n* `node` version set to v16.14.2. Version is now synced with Kit.app which resolves conflicts with native packages in scripts\r\n* Keyboard actions (copy/paste/type) have moved from applescript to native code. Snippets, setSelectedText, etc should now feel as “instant” as possible.\r\n* You can now `await hide()` for when you need to make sure the prompt is hidden before continuing the script. This was necessary since the new keyboard actions were so fast.\r\n* Moved the script sharing auth flow to a widget\r\n* Internal: Can now set the state of Kit.app from a script to help with debugging\r\n\r\n\r\n## Fixes\r\n\r\n* App launcher failed to parse malformed App plist icons\r\n* Editor whitespace collapsing on HiDPI screens\r\n* Touchbar key while prompt open would cause crash\r\n* Bin files sometimes didn’t regenerate properly when re-launching the app\r\n* Updater issues\r\n* Background UI not updating when user manually terminates process\r\n* Performance: Moved the file watcher to a spawned process that sends events to the App\r\n\r\n## More to come...\r\n\r\nFinally moved into the new house and settled in. Personal life was too hectic to stream much or do release notes on past couple releases. Expect more streaming, sharing scripts, promotion, and news from me. Cheers! 🥂\r\n\r\n_Here's a preview of the new menu and an example of a dot notification_\r\n\r\n\r\n![The new dot notifications and re-vamped menu](https://cdn.discordapp.com/attachments/963905444823318578/988844882665803926/unknown.png)\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/787","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/745","url":"https://github.com/johnlindquist/kit/discussions/745","title":"March 2022 Release (version 1.7.3) - Widgets! Built-in Terminal, Menu, and Built-in Editor Types","name":"March 2022 Release (version 1.7.3) - Widgets! Built-in Terminal, Menu, and Built-in Editor Types","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/745","createdAt":"2022-03-01T19:45:06Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AO6W1","body":"# March 2022 Release (version 1.7.3)\r\n\r\nScript Kit should auto-update or you can grab the downloads here: https://www.scriptkit.com/\r\n\r\n## Widgets - `await widget()`\r\n\r\nA widget is a detached UI window that can control and listen to a script.\r\n\r\n\r\n\r\n[Open widget-hello-world in Script Kit](https://scriptkit.com/api/new?name=widget-hello-world&url=https://gist.githubusercontent.com/johnlindquist/ca174899643e86f416d301d9599bb4e8/raw/55d334c6dc412c0346a750348d8c0ffa2b8650ba/widget-hello-world.ts\")\r\n\r\n```js\r\n// Name: Widget Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet message = await arg(\"Hello what?\")\r\nawait widget(`
Hello ${message}
`)\r\n\r\n```\r\n\r\n### Widget Events\r\n\r\n\r\n[Open widget-events in Script Kit](https://scriptkit.com/api/new?name=widget-events&url=https://gist.githubusercontent.com/johnlindquist/1ce2972fdeed0773450f4dba3f3f2c00/raw/6834ccde194ea471f403df9366a7ac283cb853bb/widget-events.ts\")\r\n\r\n```js\r\n// Name: Widget Events\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = \"\"\r\nlet count = 0\r\n\r\nlet w = await widget(`\r\n
\r\n`)\r\n})\r\n\r\nw.onInput((event) => {\r\n text = event.value\r\n})\r\n\r\nw.onMoved(({ x, y}) => {\r\n // e.g., save position\r\n})\r\n\r\nw.onResized(({ width, height }) => {\r\n // e.g., save size\r\n})\r\n```\r\n\r\n## Closing a Widget\r\nThere are 3 ways to close a widget:\r\n1. Hit \"escape\" with the widget focused\r\n2. End the process of the widget. Hit cmd+p with the main menu focused to see the running processes or `exit()` anywhere in the script.\r\n3. Use a `ttl` (time to live) in the options when creating a widget\r\n\r\n## \"Always on Top\" and Locking the Widget\r\n\r\n[Open widget-always-on-top in Script Kit](https://scriptkit.com/api/new?name=widget-always-on-top&url=https://gist.githubusercontent.com/johnlindquist/bfd8ec67d9632867b0faf4e808381948/raw/90f766f21af8c88760409215e569baef9d8f0238/widget-always-on-top.ts\")\r\n\r\n```js\r\n// Name: Widget Always on Top\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait widget(`
🇺🇦
`, {\r\n alwaysOnTop: true,\r\n transparent: true\r\n})\r\n\r\n```\r\n\r\nWith a widget focused, press cmd+l to \"Lock\" the widget. This will disable any possible mouse interactions (including moving, resizing, etc) and allow you to click through the widget to any windows underneath.\r\n\r\nTo \"Unlock\":\r\n1. three-fingered swipe up on OSX\r\n2. focus the widget\r\n3. hit cmd+l\r\n\r\nYou can now hit move, escape, etc the widget.\r\n\r\n## Built-in Terminal - `await term()`\r\n\r\n`term` is Script Kit's built-in terminal.\r\n\r\n### From the Main Menu\r\n\r\nType > into the main menu to open `term`\r\n\r\n### From a Script\r\n\r\nUse the `await term()` API to switch to the terminal.\r\n\r\n[Open term-hello-world in Script Kit](https://scriptkit.com/api/new?name=term-hello-world&url=https://gist.githubusercontent.com/johnlindquist/10420bab68da357b572c1e703c2c5a43/raw/a287e443f1e57d4541f50feba28b86e1702ff515/term-hello-world.ts\")\r\n\r\n```js\r\n// Name: Term Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait term(`echo 'Hello World!'`)\r\n```\r\n\r\n> Note: If you want spawn a new Mac terminal, use `await terminal()`\r\n\r\n### Pass Terminal Output to Script\r\n\r\nIf you end the terminal with cmd+enter, the script will continue and you can grab the latest text output from the terminal.\r\n\r\n> 🐞: ctrl+any key will also end the terminal. This is a bug (it was only meant to be ctrl+c) which I'll fix soon. I'm also open to ideas for other shortcuts to \"end\" a terminal that aren't taken by vim/emacs/etc, because I know I'll be missing some.\r\n> 🐞: `term` doesn't grab keyboard focus when opening. I'll get that fixed ASAP!\r\n\r\n[Open term-returns in Script Kit](https://scriptkit.com/api/new?name=term-returns&url=https://gist.githubusercontent.com/johnlindquist/935445caef26d1c13f195533569cd0cc/raw/ca921b09480a3d7b5bf63e7a777011199e642fb9/term-returns.ts\")\r\n\r\n```js\r\n// Name: Term Returns\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = await term(`ls ~/.kit`)\r\nawait editor(text)\r\n```\r\n\r\n## Menubar - `menu()`\r\n\r\n`menu` allows you to customize the menubar for Script Kit.\r\n\r\n\r\n\r\n[Open menu-hello-world in Script Kit](https://scriptkit.com/api/new?name=menu-hello-world&url=https://gist.githubusercontent.com/johnlindquist/6aeb6a3f916bfc40e9acd6b9d4388b34/raw/21d8c70da05f08e454d81cb0ccebbbbc82b42f7d/menu-hello-world.ts\")\r\n\r\n```js\r\n// Name: Menu Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nmenu(`Hello 🌎`)\r\n```\r\n\r\n### Menu with Scripts\r\n\r\nThe second arg of menu can be an array of scripts you wish to present in a drop-down menu. This way, on left-click, you'll get a list of scripts to pick from from the menubar rather than opening the main Script Kit UI.\r\n\r\n[Open menu-with-scripts in Script Kit](https://scriptkit.com/api/new?name=menu-with-scripts&url=https://gist.githubusercontent.com/johnlindquist/0e07e0a8bd4926be6d843ce49fbb4474/raw/4a111ceeae7725525fdcf8bf546105698a4ac4c9/menu-with-scripts.ts\")\r\n\r\n```js\r\n// Name: Menu with Scripts\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n// An empty string means \"Use default Script Kit icon\"\r\nmenu(``, [\r\n \"app-launcher\"\r\n])\r\n```\r\n\r\n## Built-in Editor Types\r\n\r\nScript Kit's built-in editor now loads all of Script Kit's types! This was a huge undertaking that everyone just expects to work. You all know how that feels 😇\r\n\r\n> 🐞: Please let me know if you see any missing. I noticed that I missed the types for `terminal` and `iterm` when putting this post together 🤦♂️.\r\n\r\n## March Plans\r\n\r\nI'm dedicating March to DOCUMENTATION!!! (and bug-fixes)... I have _a lot_ of script requests to follow-up on and work around the newsletter and other non-app stuff. I'm also moving this month, so y'all know how stressful that can be. So expect the April build to be extremely light feature-wise, but I will be set up in the new house ready to much more live-streaming and communication. Can't wait to share more! 🙂\r\n\r\n## Questions?\r\n\r\nI'm happy to help with any questions you may have!","value":"https://github.com/johnlindquist/kit/discussions/745","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/705","url":"https://github.com/johnlindquist/kit/discussions/705","title":"February 2021 (version 1.6.9) - Built-in Editor, New Main Menu Features, `await path()`, Event handlers, Beta pro features, Terminate Process","name":"February 2021 (version 1.6.9) - Built-in Editor, New Main Menu Features, `await path()`, Event handlers, Beta pro features, Terminate Process","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/705","createdAt":"2022-01-30T00:36:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOqIL","body":"Been a _busy_ month of major Script Kit features!\r\n\r\n## Built-in Editor\r\n\r\nScript Kit's number one goal is to make writing time-saving scripts easier. So now Script KIt comes with a pre-configured editor, complete with autocompletion and error checking:\r\n\r\nhttps://user-images.githubusercontent.com/36073/152349718-9f9af13f-4cbb-4444-810a-2f1281938106.mp4\r\n\r\nIf you're already using vs code, you can switch to the \"Kit\" editor in the `Kit` tab -> `Change Editor`.\r\n\r\n## Main Menu Shortcuts and `/`, `~`, and `>`\r\n\r\nThe Script Kit main menu will continue to grow in features. \r\n\r\n### Shortcuts\r\n\r\n* `cmd+f` - Does a `grep` search through all of your scripts\r\n* `cmd+p` - Launches a `Processes` menu for currently running scripts\r\n\r\n### Path Mode\r\nType the following characters to change the mode of the main menu:\r\n* ~ Switches to a path selector mode in your home directory\r\n* / Switches to a path selector mode in your root directory\r\n\r\nNavigate with right/left or tab/shift+tab then select with return. Here's an example of typing `~`\r\n\r\nhttps://user-images.githubusercontent.com/36073/152386816-8be054d6-047c-416f-ae2b-dce1723d222c.mp4\r\n\r\n### Command Mode\r\n\r\n* > Switches to a command mode to execute a command\r\n\r\n\r\nhttps://user-images.githubusercontent.com/36073/152387376-b2e8b71a-4980-4d23-8c98-4f56f3ce1fdd.mp4\r\n\r\n\r\n### Future Work\r\nIn the March release, planning on these:\r\n\r\n* , List system preferences\r\n* . App launcher\r\n* ; MEGA MENU WITH EVERYTHING 🤭\r\n\r\n## `await path()`\r\n\r\nYou can now prompt to select a path. This UI works exactly like \"path mode\" above.\r\n\r\n```js\r\nlet selectedPath = await path()\r\ncd(selectedPath)\r\n\r\nawait exec(`git pull`) // this will now operate based on the selectedPath\r\n```\r\n\r\n## Event Handlers\r\n\r\nWhen building the `path` prompt, I realized it just wasn't possible to do in a script. So I put in the effort to expose the event handlers from the app into the prompt. So even though `path` behaves very differently, it's still an `arg` with customized handlers. You can override many of the handlers yourself for customized prompts:\r\n\r\nFor example, you can override the default behavior of `Escape` terminating your current script:\r\n\r\n```js\r\n// Submit the current input when you hit escape\r\nawait arg({\r\n onEscape: (input)=> {\r\n submit(input)\r\n }\r\n})\r\n```\r\n\r\nOverriding handlers is definitely considered \"advanced\", so I'm happy to answer any questions!\r\n\r\nHere's a list of all the new `arg` config properties:\r\n```js\r\nexport interface ChannelHandler {\r\n (input: string, state: AppState): void | Promise\r\n}\r\n\r\nexport interface PromptConfig\r\n onNoChoices?: ChannelHandler\r\n onChoices?: ChannelHandler\r\n onEscape?: ChannelHandler\r\n onAbandon?: ChannelHandler\r\n onBack?: ChannelHandler\r\n onForward?: ChannelHandler\r\n onUp?: ChannelHandler\r\n onDown?: ChannelHandler\r\n onLeft?: ChannelHandler\r\n onRight?: ChannelHandler\r\n onTab?: ChannelHandler\r\n onInput?: ChannelHandler\r\n onBlur?: ChannelHandler\r\n onChoiceFocus?: ChannelHandler\r\n\r\n debounceInput?: number\r\n debounceChoiceFocus?: number\r\n\r\n onInputSubmit?: {\r\n [key: string]: any\r\n }\r\n onShortcutSubmit?: {\r\n [key: string]: any\r\n }\r\n}\r\n```\r\n\r\n## onInputSubmit, onShortcutSubmit\r\n\r\nIf you want to create \"shortcuts\" to submit specific values, can use the new `onInputSubmit` and `onShortcutSubmit`. These allow you to bind text or shortcuts to submit values. This is exactly how the main menu works:\r\n\r\n![CleanShot 2022-02-03 at 09 47 42](https://user-images.githubusercontent.com/36073/152388672-db242f4e-20e2-4645-a326-a8bbc960f63d.png)\r\n\r\n\r\n## Beta Pro Features: Menubar\r\n\r\nYou can now customize the text of the Script Kit menubar icon to say anything with the `pro.beta.menubar` method. In the future, you'll be able to build out an entire menu, but I thought I'd sneak this feature in for fun in this build:\r\n\r\n[Open menubar-demo in Script Kit](https://scriptkit.com/api/new?name=menubar-demo&url=https://gist.githubusercontent.com/johnlindquist/ef01308eb63715970f26ee1378473194/raw/9ad4219968d9ec1a9747811a71c678ad8e241ec0/menubar-demo.ts\")\r\n\r\n```js\r\n// Name: Menubar Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"Set the menubar to:\")\r\npro.beta.menubar(value)\r\n\r\n\r\n```\r\n\r\nhttps://user-images.githubusercontent.com/36073/152394319-d9e071fe-edcd-4cf2-be3e-60d5ba7b01cd.mp4\r\n\r\n\r\n## Terminate Processes\r\n\r\nIf you need to end a script that's running in the background, stuck on an exec command, or whatever reason, open the main menu with the cmd+; shortcut, then press this button (or hit cmd+p. This will open a \"terminate processes\" window where you can end your scripts:\r\n\r\n![CleanShot 2022-02-03 at 10 20 45@2x](https://user-images.githubusercontent.com/36073/152394881-cf612921-1f00-4458-861a-3538053377dd.png)","value":"https://github.com/johnlindquist/kit/discussions/705","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/669","url":"https://github.com/johnlindquist/kit/discussions/669","title":"Script Kit for Linux - Developer Preview","name":"Script Kit for Linux - Developer Preview","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/669","createdAt":"2021-12-24T08:15:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOXBA","body":"# Script Kit for Linux - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. Building from the \"Mac\" source\r\n\r\nCurrently, the Linux build builds from the exact same branch as the Mac build. While this works fine, for now, we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out details next year.\r\n\r\n2. The Linux build is missing all the OS-specific tools\r\n\r\nLinux currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n3. I've Only Tested on Ubuntu through a Parallels vm\r\n\r\nObviously will need some more real-world testing.\r\n\r\n## Where to Download\r\n\r\nDownload the AppImage here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1","value":"https://github.com/johnlindquist/kit/discussions/669","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/655","url":"https://github.com/johnlindquist/kit/discussions/655","title":"🥳 Script Kit Launch Day 🎉","name":"🥳 Script Kit Launch Day 🎉","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/655","createdAt":"2021-12-17T18:33:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOUTd","body":"# Script Kit is Officially Released! 🎉\r\n\r\nDownload now from https://scriptkit.com\r\n\r\n## Free Script Kit Course on [egghead.io](https://egghead.io)\r\n\r\nTo help you get started, I put together a short course as a feature tour:\r\n\r\nhttps://egghead.io/courses/script-kit-showcase-for-optimizing-your-everyday-workflows-e20ceab4\r\n\r\nIf you've been using Script Kit for a while on the beta, you know it can do much, much more than what's shown in the lessons, but everyone has to start somewhere. Speaking of the beta...\r\n\r\n## Beta Channel Discontinued\r\n\r\nIf you installed the beta, please download from https://scriptkit.com, quit Kit.app, and replace with the new version. This will put you on the “Main” channel. Updates will be ~monthly. The beta channel is discontinued ❗️\r\n\r\nAlso, thank you so, so much for all your feedback and patience with updates over the past year. You’ve really helped make Script Kit what it is today and I’m forever grateful 🙏\r\n\r\n## Windows Developer Preview\r\n\r\nThe details for the Windows build are found here: https://github.com/johnlindquist/kit/discussions/654\r\n\r\n## Plans for 2022\r\n\r\n1. Make the dev setup more contribution-friendly. I would love to accept PRs later next year.\r\n2. Get the Windows build to parity with Mac.\r\n3. Lots of lessons and scripts. I can finally spend more time sharing scripts than working on the app 😎\r\n4. Research into Rust, runtimes, and utilities that can provide any benefit to making our scripts better.\r\n5. Focus on \"export to serverless function\", \"export as github action\", and other ways to maximize the work you put into your scripts.\r\n5. Script Kit Pro. A paid version with additional features not found in the base version. Not ready to talk about it, but it's exciting!\r\n","value":"https://github.com/johnlindquist/kit/discussions/655","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/654","url":"https://github.com/johnlindquist/kit/discussions/654","title":"Script Kit for Windows - Developer Preview","name":"Script Kit for Windows - Developer Preview","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/654","createdAt":"2021-12-17T15:06:29Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOUJx","body":"# Script Kit for Windows - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. I haven't bought a certificate to add to the build:\r\n- You'll see many \"untrusted\" warnings when downloading/installing\r\n- Auto-updating will not work\r\n\r\n2. I haven't decided if the Windows repo will be a fork, branch, or main\r\n\r\nCurrently, the Windows build builds from the exact same branch as the Mac build. While this works fine, for now, I'm pretty sure we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out the details next year.\r\n\r\n3. The Windows build is missing all the OS-specific tools\r\n\r\nWindows currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n4. I've Only Tested It on Two Laptops\r\n\r\nThe Mac version has been used/tested by many, many people. I have two Windows laptops at home to test it on. It works well, but I don't know how much your mileage will vary.\r\n\r\n## Where to Download\r\n\r\nDownload the installer here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1\r\n\r\nAgain, this build will not auto-update. I'll post announcements here when new versions are available and you'll have to download the new version each time until I have the certificate and release servers worked out. Honestly, I'll probably write a \"check for Windows update and download\" script then you can just run that on a `// Schedule: 0 8 * * *` 😉","value":"https://github.com/johnlindquist/kit/discussions/654","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/615","url":"https://github.com/johnlindquist/kit/discussions/615","title":"beta.114 - Info, Settings, Choice Events 🎛","name":"beta.114 - Info, Settings, Choice Events 🎛","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/615","createdAt":"2021-11-22T19:09:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOHN6","body":"# beta.114 - Info, Settings, Choice Events\r\n\r\n## Displaying Temporary Info\r\n\r\nUntil now, `await div()` worked by waiting for the user to hit enter/escape. This still works fine, but if you want to \"timeout\" a `div` to display temporary info without user input, this entire script will run without any user interaction:\r\n\r\n[Install non-blocking-div](https://scriptkit.com/api/new?name=non-blocking-div&url=https://gist.githubusercontent.com/johnlindquist/87e92156251d09a02154f04772f1e9bf/raw/be6dde40a7f5e1f3b8eaa9abf68d8698031cd3de/non-blocking-div.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet classes = `p-5 text-3xl flex justify-center items-center text-center`\r\n\r\ndiv(`Wait 1 second...`, classes)\r\nawait wait(1000)\r\n\r\ndiv(`Just 2 more seconds...`, classes)\r\nawait wait(2000)\r\n\r\ndiv(`Almost there...`, classes)\r\nawait wait(3000)\r\n\r\n```\r\n\r\n## Remember Selection\r\n\r\nI needed to build a settings \"panel\", so I wanted to make a list that could toggle. \r\n\r\n\r\n![CleanShot 2021-11-22 at 12 08 29](https://user-images.githubusercontent.com/36073/142920816-3bf47911-578b-4e2f-9662-10257287fde4.png)\r\n\r\nThe solution was to remember the previous choice by `id`. Any time `arg` is invoked, it will check to see if a choice has an id that matched the previously submitted choice and focus back on it. This enables you to hit enter repeatedly to toggle a choice on and off.\r\n\r\n[Install remember-selection](https://scriptkit.com/api/new?name=remember-selection&url=https://gist.githubusercontent.com/johnlindquist/a86395d809c260d943f9763023f5a6f0/raw/4c1057b8500fcdf34fcd179af52f09cc7dee9ca4/remember-selection.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Off\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n]\r\n\r\nlet argConfig = {\r\n placeholder: \"Toggle items\",\r\n flags: {\r\n end: {\r\n shortcut: \"cmd+enter\",\r\n },\r\n },\r\n}\r\n\r\nwhile (true) {\r\n let item = await arg(argConfig, data)\r\n data.find(i => i.id === item.id).name =\r\n item.name === \"On\" ? \"Off\" : \"On\"\r\n\r\n if (flag.end) break\r\n}\r\n\r\nawait div(JSON.stringify(data), \"p-2 text-sm\")\r\n```\r\n\r\nYou could also use this when making a sequence of selections:\r\n\r\n[Install remember-sequence](https://scriptkit.com/api/new?name=remember-sequence&url=https://gist.githubusercontent.com/johnlindquist/80f9d005e5bff92691125f736199aa2c/raw/4e05c118bc91defe5e2f39cff20eb9862f4c6a2d/remember-sequence.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"One\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Two\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Three\",\r\n },\r\n]\r\n\r\nlet selections = []\r\n\r\nlet one = await arg(`First selection`, data)\r\nselections.push(one)\r\n\r\nlet two = await arg(\r\n {\r\n placeholder: `Second selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(two)\r\n\r\nlet three = await arg(\r\n {\r\n placeholder: `Third selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(three)\r\n\r\nawait div(\r\n selections.map(s => s.name).join(\", \"),\r\n \"p-2 text-sm\"\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n[Install no-choices-event](https://scriptkit.com/api/new?name=no-choices-event&url=https://gist.githubusercontent.com/johnlindquist/5534589a322bbb384e5bf4dbcbf00864/raw/1a7c2500149db3b8731e900646d568fa7fb5ed74/no-choices-event.js\")\r\n\r\n## Choice Events\r\n\r\n`onNoChoices` and `onChoices` allows Kit.app to tell your script when the user has typed something that filtered out every choice. Most commonly, you'll want to provide a `setHint` (I almost made it a default), but you can add any logic you want.\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\r\n {\r\n placeholder: `Pick a fruit`,\r\n onChoices: async () => {\r\n setHint(``)\r\n },\r\n onNoChoices: async input => {\r\n setHint(`No choices matched ${input}`)\r\n },\r\n },\r\n [`Apple`, `Orange`, `Banana`]\r\n)\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/615","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/587","url":"https://github.com/johnlindquist/kit/discussions/587","title":"beta.98 - Previews 👀, Docs, devTools, updater improvements","name":"beta.98 - Previews 👀, Docs, devTools, updater improvements","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/587","createdAt":"2021-11-12T17:32:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOC-z","body":"## Previews\r\n\r\nCreating the previews feature was a huge undertaking, but it really paid off. You can now render html into a side pane by simply providing a `preview` function. A preview can be a simple string all the way to an async function per choice that loads data based on the currently selected choice. For examples, see here #555 \r\n\r\n> You can toggle previews on/off with cmd+p\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507471-db3e4454-f0ef-4e6b-891b-bd4344a40e85.mp4\r\n\r\n## Docs\r\n\r\nAlong with previews comes the built-in docs.\r\n\r\n- Docs are built from the GitHub discussions [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) category\r\n- Each time I post/update a doc, a webhook builds the docs into a json file, Kit.app checks for a new docs.json once a day (or you can manually update them from the `Help->Download Latest Docs`\r\n- You can _click an example to install it!_ 🎉\r\n- I'll keep working on docs and examples. Please ask any questions over in the [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) section if you'd like to see something clarified.\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507953-02d44174-3ac0-43d7-8d92-4319e917d512.mp4\r\n\r\n## Dev Tools\r\n\r\nPass any data into `devTools` to pop open a Dev Tools pane so you can interact with the data. `devTools` will first log out the data, but it's also assigned to an `x` variable you can interact with in the console.\r\n\r\n> `devTools` will be another paid feature once Script Kit 1.0 releases\r\n\r\nhttps://user-images.githubusercontent.com/36073/141508954-df3ea997-a49e-4fdd-bd40-7bff76024a6d.mp4\r\n\r\n## Updater Fixes\r\n\r\nA few users reported a strange behavior with the updater. If you've had any issues with it, please download a fresh copy of Kit.app from https://scriptkit.com and overwrite the old version. There are many more guards around the updating logic to prevent those issues from cropping up again.\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/587","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/488","url":"https://github.com/johnlindquist/kit/discussions/488","title":"Script Kit online on Stackblitz ⚡️","name":"Script Kit online on Stackblitz ⚡️","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/488","createdAt":"2021-10-18T20:06:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AN3Xv","body":"I spent last week getting Script Kit running \"in browser\" to emulate the terminal experience over on Stackblitz. Here's a quick demo:\r\n\r\nhttps://stackblitz.com/edit/node-rnrhra?file=scripts%2Frepos-to-markdown.js\r\n\r\nThe plan is to use this to host interactive demos for the guide/docs. I'd appreciate if you could play around with it a bit and see if I missed anything.\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/488","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/457","url":"https://github.com/johnlindquist/kit/discussions/457","title":"TypeScript support! 🚀","name":"TypeScript support! 🚀","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/457","createdAt":"2021-09-27T17:25:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ANtu1","body":"beta.62 brings with it a long-awaited, much-requested feature: TypeScript support!\r\n\r\n![CleanShot 2021-09-27 at 10 42 38](https://user-images.githubusercontent.com/36073/134951810-31754840-85c3-4ad3-a493-59c757fdda07.png)\r\n\r\n## TypeScript Support 🚀\r\n\r\n### 1. But, how?\r\n\r\nEach time your run a TS script, Script Kit will compile the TS script using `esbuild` to a JS script in a `.scripts` dir (notice the \"dot\"). The compiled JS script is then imported from there. Using `.scripts` as a sibling dir will help avoid any `import`/path issues. You can also write TS \"library\" files in your `~/.kenv/lib` dir and import them into your script just fine.\r\n\r\nIf you're experienced with `esbuild` and curious about the settings, they look like this:\r\n\r\n```js\r\nlet { build } = await import(\"esbuild\")\r\n\r\nawait build({\r\n entryPoints: [scriptPath],\r\n outfile,\r\n bundle: true,\r\n platform: \"node\",\r\n format: \"esm\",\r\n external: [\"@johnlindquist/kit\"],\r\n})\r\n```\r\n\r\nThis also opens the door to exporting/building/bundling scripts and libs as individual shippable tools which I'll investigate more in the future.\r\n\r\n### 2. Can I still run my JS scripts if I switch to TS?\r\n\r\nYes! Both your TS and JS scripts will show up in the UI.\r\n\r\n### 3. Why the `import \"@johnlindquist/kit\"`?\r\n\r\nWhen you create a new TS script, the generated script will start with the line: `import \"@johnlindquist/kit\"`\r\n\r\nThis is mostly to make your editor stop complaining by forcing it to load the type definition files and forcing it to treat the file as an \"es module\" so support \"top-level `await`\". It's not technically required since it's not technically importing anything, but your editor will certainly complain very loudly if you leave it out.\r\n\r\n### 4. Where is the setting stored?\r\n\r\nLook in your `~/.kenv/.env` for `KIT_MODE=ts`.\r\n\r\n## fs-extra's added to global\r\n\r\nThe [fs-extra methods](https://www.npmjs.com/package/fs-extra#methods) are now added on the global space. I found myself using `outputFile`, `write/readJson`, etc too often and found them to be a great addition. The only one missing is `copy` since we're already using that to \"copy to clipboard\". You can bring it in with the normal import/alias process if needed, e.g., `let {copy:fsCopy} = await import(\"fs-extra\")`\r\n\r\n## Sync Path\r\n\r\n![CleanShot 2021-09-27 at 11 10 26](https://user-images.githubusercontent.com/36073/134954703-7c9d779f-268a-4f8b-973a-59ac71eebaf0.png)\r\n\r\nYou may notice running scripts from the Script Kit app that some commands you can run in your terminal might be missing, like \"yarn\", etc.\r\n\r\nRun the following command in your terminal to copy the $PATH var from your terminal to your `~/.kenv/.env`. This will help \"sync\" up which commands are available between your terminal and running scripts from the app.\r\n\r\n```bash\r\n~/.kit/bin/kit sync-path\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/457","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/442","url":"https://github.com/johnlindquist/kit/discussions/442","title":"Scripts in GitHub actions (preview)","name":"Scripts in GitHub actions (preview)","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/442","createdAt":"2021-09-21T19:34:15Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ANrYA","body":"## tl;dr Here's an example repo\r\n\r\nThe example script creates a release, downloads an image, and uploads it to the release.\r\n\r\nhttps://github.com/johnlindquist/kit-action-example\r\n\r\n## Template Repo\r\n\r\nThis page has a \"one-click\" clone so you can add/play with your own script.\r\n\r\nhttps://github.com/johnlindquist/kit-action-template\r\n\r\n## What is it?\r\n\r\nUse any of your scripts in a GitHub action. `use` the `kit-action` and point it to a scripts in your `scripts` dir:\r\n\r\n```yml\r\nname: \"example\"\r\non:\r\n workflow_dispatch:\r\n pull_request:\r\n push:\r\n branches:\r\n - main\r\n\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\" # The name of a script in your ./scripts dir\r\n```\r\n\r\n## Add env vars:\r\n\r\nYou most likely add [\"secrets\" to GitHub actions](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-an-environment), so you'll want to pass them to your scripts as environment variables:\r\n\r\n```yml\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\"\r\n env:\r\n REPO_TOKEN: \"${{ secrets.REPO_TOKEN }}\" # load in your script with await env(\"REPO_TOKEN\")\r\n```\r\n\r\n## Works with your existing repos\r\n\r\nFeel free to add this action and a `scripts` dir to your existing repos. It automatically loads in your repo so you can parse `package.json`, compress assets, or whatever it is you're looking to add to your CI.\r\n\r\n## What does \"preview\" mean?\r\n\r\nEverything is working, but it's pointing to the \"main\" branch rather than a tagged version. Once I get some feedback, I'll tag a \"1.0\" version so you can `uses: @johlindquist/kit-action@v1`\r\n\r\n## Please ask for help! 😇\r\n\r\nI'd ❤️ to help you script something for a github action! Please let me know whatever I can do to help.","value":"https://github.com/johnlindquist/kit/discussions/442","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/405","url":"https://github.com/johnlindquist/kit/discussions/405","title":"beta.55 Improved Search, Drag, and Happiness 😊","name":"beta.55 Improved Search, Drag, and Happiness 😊","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/405","createdAt":"2021-08-20T21:58:48Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNTMxOTA2","body":"## Search Improvements\r\n\r\nbeta.55 has a vastly improved search:\r\n\r\nSearch descriptions 🎉\r\n\r\n![CleanShot 2021-08-20 at 13 37 44](https://user-images.githubusercontent.com/36073/130285547-24f111d7-a706-4be2-b0d6-b425afbe6683.png)\r\n\r\nSearch shortcuts\r\n\r\n![CleanShot 2021-08-20 at 13 51 49](https://user-images.githubusercontent.com/36073/130286634-4797c029-c2ed-4071-9e1d-285d6bf1a15f.png)\r\n\r\nSearch by kenv\r\n\r\n![CleanShot 2021-08-20 at 13 51 18](https://user-images.githubusercontent.com/36073/130286567-4fd0a155-43a7-4af5-bd3f-c0a9db9ae8dc.png)\r\n\r\nSear by \"command-name\" (if you can't think of // Menu: name)\r\n\r\n![CleanShot 2021-08-20 at 13 54 45](https://user-images.githubusercontent.com/36073/130286878-62b2a139-b3b7-4c34-904f-40d7b03a7e1c.png)\r\n\r\nSorts by \"score\" (rather than alphabetically)\r\n\r\n## Drag\r\n\r\nChoices can now take a `drag` property. This will make list items \"draggable\" and allow you to drag/drop to copy files from your machine (or even from URLs!) into any app. When using remote URLs, their will be a bit of \"delay\" while the file downloads (depending on the file size) between \"drag start\" and \"drop enabled\", so just be aware. I'll add some sort of download progress indicator sometime in the future, just not high priority 😅\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Heart Eyes (local)\",\r\n drag: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n img: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n },\r\n {\r\n name: \"React logo svg (wikipedia)\",\r\n drag: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n img: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 26 07](https://user-images.githubusercontent.com/36073/130294979-c45aabe2-6c30-41ad-94b4-64a85a2c34eb.gif)\r\n\r\nYou can use the `drag` object syntax to define a `format` and `data`\r\n\r\n> `text/html`: Renders the HTML payload in contentEditable elements and rich text (WYSIWYG) editors like Google Docs, Microsoft Word, and others.\r\n> `text/plain`: Sets the value of input elements, content of code editors, and the fallback from text/html.\r\n> `text/uri-list`: Navigates to the URL when dropping on the URL bar or browser page. A URL shortcut will be created when dropping on a directory or the desktop.\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Padding 4\",\r\n drag: {\r\n format: \"text/plain\",\r\n data: `className=\"p-4\"`,\r\n },\r\n },\r\n {\r\n name: \"I love code\",\r\n drag: {\r\n format: \"text/html\",\r\n data: `I ❤️ code`,\r\n },\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 48 00](https://user-images.githubusercontent.com/36073/130296713-6249d5c2-c01f-42d1-b2c2-ea86d2e4c29b.gif)\r\n\r\n## Happiness\r\n\r\nI'm _very_ happy with the state of Script Kit. When I started almost a year ago, I had no idea I could push the concept of creating/sharing/managing custom scripts so far. I think it looks great, feels speedy, and is flexible enough to handle so, so many scenarios.\r\n\r\nWith everything in place, next week I'm starting on creating lessons, demos, and docs. It's time to show you what Script Kit can really do 😉 \r\n\r\nP.S. - Thanks for all the beta-testing and feedback. It's been tremendously helpful!\r\n","value":"https://github.com/johnlindquist/kit/discussions/405","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/397","url":"https://github.com/johnlindquist/kit/discussions/397","title":"beta.46 Design, ⚐ Flags, div, fixed notify","name":"beta.46 Design, ⚐ Flags, div, fixed notify","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/397","createdAt":"2021-08-13T16:33:27Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNTE2OTE1","body":"## Design/theme\r\n\r\nPut a lot of work into tightening up pixels and made progress towards custom themes:\r\n\r\n![CleanShot 2021-08-13 at 09 35 40](https://user-images.githubusercontent.com/36073/129383567-ae628c68-3c96-463f-a47e-4800186ea7ac.png)\r\n\r\nHere's a silly demo of me playing with theme generation:\r\n\r\nhttps://user-images.githubusercontent.com/36073/129384214-2af744ab-8165-4e3f-825d-42fadbf86aec.mp4\r\n\r\n## Flags ⚐\r\n\r\nAn astute observer would notice that the `Edit` and `Share` tabs are now gone. They've been consolidated into a \"flag menu\".\r\n\r\nWhen you press the `right` key from the main menu of script, the flag menu now opens up. This shows the selected script and gives you some options. It also exposes the keyboard shortcuts associated with those options that you can use to :\r\n\r\n![CleanShot 2021-08-13 at 09 42 52](https://user-images.githubusercontent.com/36073/129384559-bff59ebf-88d9-4b95-b9b5-640ce755fe8f.png)\r\n\r\nI've found I use `cmd+o` and `cmd+n` all the time to tweak scripts of quickly create a new one to play around with.\r\n\r\n### Custom Flags\r\n\r\nYou can pass your own custom flags like so:\r\n\r\n[Install flags-demo](https://scriptkit.com/api/new?name=flags-demo&url=https://gist.githubusercontent.com/johnlindquist/b96c8f8de9c256f909ae0f6ab0adda39/raw/9f049cf454f0766fb278e5ee7a24c6b6776df889/flags-demo.js)\r\n\r\n```js\r\n//Menu: Flags demo\r\n\r\nlet urls = [\r\n \"https://scriptkit.com\",\r\n \"https://egghead.io\",\r\n \"https://johnlindquist.com\",\r\n]\r\n\r\nlet flags = {\r\n open: {\r\n name: \"Open\",\r\n shortcut: \"cmd+o\",\r\n },\r\n copy: {\r\n name: \"Copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n}\r\n\r\nlet url = await arg(\r\n { placeholder: `Press 'right' to see menu`, flags },\r\n urls\r\n)\r\n\r\nif (flag?.open) {\r\n $`open ${url}`\r\n} else if (flag?.copy) {\r\n copy(url)\r\n} else {\r\n console.log(url)\r\n}\r\n```\r\n\r\nNotice that `flag` is a global while `flags` is an object you pass to `arg`. This is to help keep it consistent with terminal usage:\r\n\r\nFrom the terminal\r\n```bash\r\nflags-demo --open\r\n```\r\n\r\nWill set the global `flag.open` to `true`\r\n\r\n![CleanShot 2021-08-13 at 10 08 30](https://user-images.githubusercontent.com/36073/129388037-3f27a12d-9e44-4402-a51f-bac39eead54d.png)\r\n\r\n\r\nYou could also run this and pass in all the args:\r\n\r\n```bash\r\nflags-demo https://egghead.io --copy\r\n```\r\n\r\nIn the app, you could create a second script to pass flags to the first with. This is required if you need to pass multiple flags since the `arg` helper can only \"submit\" one per `arg`.\r\n\r\n```js\r\nawait run(`flags-demo https://egghead.io --copy`)\r\n```\r\n\r\nI'll put together some more demos soon. There are plenty of existing CLI tools out there using flags heavily, so lots of inspiration to pull from.\r\n\r\n## `await div()`\r\n\r\nThere's a new `div` \"component\". You can pass in arbitrary HTML. This works well with the `md()` helper which generates `html` from markdown.\r\n\r\n[Install div-demo](https://scriptkit.com/api/new?name=div-demo&url=https://gist.githubusercontent.com/johnlindquist/0ad790953f7101d313abfd48182356b0/raw/c70e17649317986707d2ac714c31afe6f7850015/div-demo.js)\r\n\r\n```js\r\n// Menu: Div Demo\r\n\r\n// Hit \"enter\" to continue, escape to exit\r\nawait div(``)\r\n\r\nawait div(\r\n md(\r\n `\r\n # Some header\r\n\r\n ## You guessed it, an h2\r\n\r\n * I\r\n * love\r\n * lists\r\n `\r\n )\r\n)\r\n\r\n```\r\n\r\n## Fixed `notify`\r\n\r\n`notify` is now fixed so that it doesn't open a prompt\r\n\r\nThe most basic usage is:\r\n\r\n```js\r\nnotify(\"Hello world\")\r\n```\r\n\r\n`notify` leverages [https://www.npmjs.com/package/node-notifier](https://www.npmjs.com/package/node-notifier)\r\n\r\nSo the entire API should be available. Here's an example of using the \"type inside a notification\":\r\n\r\n[Install notify-demo](https://scriptkit.com/api/new?name=notify-demo&url=https://gist.githubusercontent.com/johnlindquist/44387dc5b0c170e4146b061162c33532/raw/1bce77fb778a45cf9052a63d02dcab94a9cf7ef0/notify-demo.js)\r\n\r\n```js\r\n// Menu: Notify Demo\r\nlet notifier = notify({\r\n title: \"Notifications\",\r\n message: \"Write a reply?\",\r\n reply: true,\r\n})\r\n\r\nnotifier.on(\"replied\", async (obj, options, metadata) => {\r\n await arg(metadata.activationValue)\r\n})\r\n\r\n```\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/397","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/365","url":"https://github.com/johnlindquist/kit/discussions/365","title":"beta.33 `console.log` component, cmd+o to Open, `className`","name":"beta.33 `console.log` component, cmd+o to Open, `className`","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/365","createdAt":"2021-07-22T22:44:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDczNTEw","body":"## `console.log` Component\r\n\r\nThe follow code will create the below prompt (👀 notice the black background logging component):\r\n```js\r\nlet { stdout } = await $`ls ~/projects | grep kit`\r\n\r\nawait arg(`Select a kit dir`, stdout.split(\"\\n\"))\r\n\r\n```\r\n\r\n\r\n```js\r\nconsole.log(chalk`{green.bold The current date is:}`)\r\nconsole.log(new Date().toLocaleDateString())\r\nawait arg()\r\n```\r\n\r\n\r\n\r\nThe log even persists between prompts:\r\n\r\n```js\r\nlet first = await arg(\"First name\")\r\nconsole.log(first)\r\nlet last = await arg(\"Last name\")\r\nconsole.log(`${first} ${last}`)\r\nlet age = await arg(\"Age\")\r\nconsole.log(`${first} ${last} ${age}`)\r\nlet emotion = await arg(\"Emotion\")\r\nconsole.log(`${first} ${last} ${age} ${emotion}`)\r\nawait arg()\r\n```\r\n\r\n\r\nClick the \"edit\" icon to open the full log in your editor:\r\n![CleanShot 2021-07-22 at 16 20 57@2x](https://user-images.githubusercontent.com/36073/126716749-4eda367a-4e55-424f-915a-30207583cd3f.png)\r\n\r\n## cmd+o to Open\r\n\r\nFrom the main menu, hitting `cmd+o` will open:\r\n\r\n1. The currently selected script from the main menu\r\n2. The currently running script\r\n3. Any \"choice\" that provides a \"filePath\" prop:\r\n\r\n```js\r\nawait arg(`cmd+o to open file`, [\r\n {\r\n name: \"Karabiner config\",\r\n filePath: \"~/.dotfiles/karabiner/karabiner.edn\",\r\n },\r\n {\r\n name: \"zshrc\",\r\n filePath: \"~/.zshrc\",\r\n },\r\n])\r\n```\r\n\r\nI've found this really useful when I want to tweak the running script, but I don't want to go back through the process of finding it.\r\n\r\n## Experimental `className`\r\n\r\nYou can pass `className` into the arg options to affect the container for the list items or panel. Most classes from Tailwind should be available. Feel free to play around with it and let me know how it goes 😇:\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n `\r\n
Working on Script Kit today
\r\n `\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n [\"Eat\", \"more\", \"tacos 🌮\"]\r\n)\r\n```\r\n\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/365","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/353","url":"https://github.com/johnlindquist/kit/discussions/353","title":"beta.29 M1 build, install remote kenvs, polish, upcoming lessons","name":"beta.29 M1 build, install remote kenvs, polish, upcoming lessons","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/353","createdAt":"2021-07-16T18:29:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDY0MDk2","body":"I'm starting on lessons/docs on Monday. If you have anything specific you want me to cover, please reply below!\r\n\r\n## M1 Build\r\n If you're on an M1 mac, you can download the new M1 build from https://www.scriptkit.com/\r\n\r\n1. Download https://www.scriptkit.com/\r\n2. Quit Kit. *note - typing `kit quit` or `k q` in the app is the fastest way to quit.\r\n3. Drag/drop to overwrite your previous build\r\n4. Kit should now auto-update from the M1 channel\r\n5. Open Kit\r\n\r\n## Kenv Management\r\nThere are a lot of tools to help manage other kenvs. They're in the `Kit` menu and once you've installed a remote kenv (which is really just a git repo with a scripts dir), then more options show up in the `Edit` menu to move scripts between kenvs, etc. I'll cover this in detail in the docs/lessons\r\n\r\n## Polish\r\nLots of UI work:\r\n* Remembering position - Each script with a `//Shortcut` will remember its last individual prompt position. For example, if you have a script that uses `textarea`, then drag it to the upper right, the next time you launch that script, it will launch in that position.\r\n* `//Image` metadata - Scripts can now have images:\r\n```js\r\n//Image: https://placekitten.com/64\r\n```\r\nor\r\n```js\r\n//Image: logo.png\r\n```\r\nwill load from `~/.kenv/assets/logo.png`\r\n\r\n\r\n* Spinner - added a spinner for when you submit a prompt and the process needs to do some work before opening the next prompt\r\n\r\n![CleanShot 2021-07-16 at 12 22 58](https://user-images.githubusercontent.com/36073/125992326-7b6f0034-00e8-41df-9ca7-f0e33becf0b2.gif)\r\n\r\n\r\n* Resizing - *Lots* of work on getting window resizing behavior consistent between different UIs. This was a huge pain, but you'll probably never appreciate it 😅\r\n* Lots more - many more small things\r\n\r\n## Lessons!\r\n\r\nI'm starting to work on lessons next week and getting back into streaming schedule. I would ♥️ to hear any specific questions or lessons you would like to see to help you remove some friction from your day. I'll be posting the lessons over on [egghead.io](egghead.io) for your viewing pleasure. Please ask questions in the replies!\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/353","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/330","url":"https://github.com/johnlindquist/kit/discussions/330","title":"Beta.20 MOAR SPEED! ⚡️","name":"Beta.20 MOAR SPEED! ⚡️","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/330","createdAt":"2021-06-26T17:03:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDMxOTgz","body":"## Process Pools and Virtualized Lists\r\n\r\nhttps://user-images.githubusercontent.com/36073/123519955-7fdd8200-d66b-11eb-8167-09b9daed1c9f.mp4\r\n\r\n\r\n## Experimental `textarea`\r\n\r\nFeel free to play around with the `textarea` for multiline input.\r\n\r\n```js\r\nlet value = await textarea()\r\n```\r\n\r\nThe API of textarea will change (it currently just sets the placeholder), but it will always return the string value of the textarea, so there won't be any breaking changes if you just keep the default behavior. `cmd+s` submits. `cmd+w` cancels.\r\n\r\n## Experimental `editor` (this will become a _paid_ 💵 feature later this year)\r\n\r\nAs an upgrade to `textarea`, `await editor()` will give you a full editor experience. Same as the textarea, the API will also change, but will always return a string of the content.\r\n\r\n\r\n```js\r\n// Defaults to markdown\r\nlet value = await editor()\r\n```\r\n\r\n> ⚠️ API is subject to change!\r\n```js\r\nlet value = await editor(\"markdown\", `\r\n## Preloaded content\r\n\r\n* nice\r\n`)\r\n```\r\n\r\n```js\r\nlet value = await editor(\"javascript\", `\r\nconsole.log(\"Support other languages\")\r\n`)\r\n```\r\n\r\n### A note on paid features\r\n\r\nEverything you've used so far in the Script Kit app will stay free. The core `kit` is open-source MIT. \r\n\r\nThe paid features will be add-ons to the core experience: Themes, Editor, Widgets, Screenshots, Record Audio, and many more fun ideas. These will roll out experimentally in the free version first then move exclusively to the paid version. Expect the paid versions later this year.\r\n","value":"https://github.com/johnlindquist/kit/discussions/330","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/312","url":"https://github.com/johnlindquist/kit/discussions/312","title":"Beta.19 New Features - Gotta go fast! 🏎💨","name":"Beta.19 New Features - Gotta go fast! 🏎💨","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/312","createdAt":"2021-06-07T20:47:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDAwNjgx","body":"Beta.19 is all about _speed_! I've finally landed on an approach I love to get the prompt moving waaaay faster.\r\n\r\nCouple videos below:\r\n\r\n## Instant Prompts\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084054-6d270a00-c79d-11eb-8b37-96473de7e0e4.mp4\r\n\r\n```js\r\n// Shortcut: option 5\r\n\r\nlet { items } = await db(async () => {\r\n let response = await get(\r\n `https://api.github.com/users/johnlindquist/repos`\r\n )\r\n\r\n return response.data\r\n})\r\n\r\nawait arg(\"Select repo\", items)\r\n\r\n```\r\n\r\n## Instant Tabs\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084134-85972480-c79d-11eb-9e18-94e5a2efa5d1.mp4\r\n\r\n## Instant Main Menu\r\n\r\nThe main menu now also leverages the concepts behind Instant Prompts listed above.\r\n\r\n## Faster in the future\r\n\r\nThese conventions laid the groundwork for caching prompt data, but I still have plenty ideas to speed things, especially around how the app launches the process. I'm looking forward to making this even faster for you!\r\n\r\nI'm also starting the work on an \"Instant Textarea\" because I know popping open a little textarea to take/save notes/ideas is something many people would use. 📝\r\n\r\n\r\n\r\n\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/312","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/305","url":"https://github.com/johnlindquist/kit/discussions/305","title":"How to Get Your Scripts Featured on ScriptKit.com 😎","name":"How to Get Your Scripts Featured on ScriptKit.com 😎","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/305","createdAt":"2021-06-04T20:07:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzk3MTc2","body":"TL;DR\r\n\r\n- Help -> Create kenv\r\n- Git init new kenv, push to github\r\n- Reply, dm, contact me somehow with the repo 😇\r\n\r\nHere's a video walking you through it:\r\n\r\nhttps://user-images.githubusercontent.com/36073/120856653-6732ee00-c53d-11eb-9dfb-04907b036361.mp4\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/305","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/304","url":"https://github.com/johnlindquist/kit/discussions/304","title":"Beta.18 Changes/Features (`db` has a breaking change)","name":"Beta.18 Changes/Features (`db` has a breaking change)","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/304","createdAt":"2021-06-04T18:09:55Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzk3MDY0","body":"## ⚠️Breaking: New `db` helper\r\n\r\n[lowdb](https://github.com/typicode/lowdb) updated to 2.0, so I updated the `db` helper to support it.\r\n\r\n* access/mutate the objects in the db directly. Then `.write()` to save your changes to the file.\r\n* `await db()` and `await myDb.write()`\r\n\r\nExample with a simple object:\r\n```js\r\nlet shoppingListDb = await db(\"shopping-list\", {\r\n list: [\"apples\", \"bananas\"],\r\n})\r\n\r\nlet item = await arg(\"Add to list\")\r\nshoppingListDb.list.push(item)\r\nawait shoppingListDb.write()\r\n\r\nawait arg(\"Shopping list\", shoppingListDb.list)\r\n```\r\n\r\n\r\nYou can also use an `async` function to store the initial data:\r\n```js\r\nlet reposDb = await db(\"repos\", async () => {\r\n let response = await get(\r\n \"https://api.github.com/users/johnlindquist/repos\"\r\n )\r\n\r\n return {\r\n repos: response.data,\r\n }\r\n})\r\n\r\nawait arg(\"Select repo\", reposDb.repos)\r\n```\r\n\r\n## Text Area prompt\r\n\r\n```js\r\nlet text = await textarea()\r\n\r\ninspect(text)\r\n```\r\n![CleanShot 2021-06-04 at 14 25 12](https://user-images.githubusercontent.com/36073/120858988-cf370380-c540-11eb-8b79-5483ec090dd8.gif)\r\n\r\n\r\n## Optional `value`\r\n\r\n`arg` choice objects used to require a `value`. Now if you don't provide a value, it will simply return the entire object:\r\n\r\n```js\r\nlet person = await arg(\"Select\", [\r\n { name: \"John\", location: \"Chair\" },\r\n { name: \"Mindy\", location: \"Couch\" },\r\n])\r\n\r\nawait arg(person.location)\r\n```\r\n\r\n## ⚗️ Experimental \"Multiple kenvs\"\r\n\r\nThere was a _ton_ 🏋️♀️ of internal work over the past couple weeks to get this working. The \"big idea\" is supporting multiple kit environments. For example:\r\n\r\n* private/personal kenv\r\n* shared kenv\r\n* company kenv\r\n* product kenv\r\n\r\n### Future plans\r\nIn an upcoming release: \r\n* you'll be able to \"click to install kenv from repo\" (just like we do with individual scripts)\r\n* update a git-controlled kenv (like a company kenv)\r\n* the main prompt will be able to search for all scripts across kenvs. \r\n* If multiple kenvs exist, creating a new script will ask you which kenv to create it in.\r\n\r\nFor now, you can try adding/creating/switching the help menu. It should all work fine, but will be _waaaay_ cooler in the future 😎\r\n\r\n![CleanShot 2021-06-04 at 11 50 32](https://user-images.githubusercontent.com/36073/120843227-16b29500-c52b-11eb-974c-a81c260b9ae2.png)\r\n\r\n## Improved Error Prompt\r\n\r\nNow when an error occurs, it takes the error data, shuts down the script, then prompts you on what to do. For example, trying to use the old `db` would result in this:\r\n\r\n![CleanShot 2021-06-04 at 12 03 04](https://user-images.githubusercontent.com/36073/120844575-d6541680-c52c-11eb-8d12-c7c3117e132e.png)\r\n\r\n## Improved Tab Switching\r\nSwitching tabs will now cancel the previous tabs' script. Previously, if you quickly switched tabs on the main menu, the \"Hot\" tab results might show up in a different tab because the loaded _after_ the tab switched. The internals around message passing between the script and the app now have a cancellation mechanism so you only get the latest result that matches the prompt/tab. (This was also a ton of internals refactoring work 😅)\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/304","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/282","url":"https://github.com/johnlindquist/kit/discussions/282","title":"✨NEW FEATURES✨ beta.17","name":"✨NEW FEATURES✨ beta.17","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/282","createdAt":"2021-05-18T20:24:57Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzcxNjg5","body":"New features are separated into the comments below:\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/282","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/245","url":"https://github.com/johnlindquist/kit/discussions/245","title":"✨ NEW ✨ // Background: true","name":"✨ NEW ✨ // Background: true","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/245","createdAt":"2021-05-06T19:36:22Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzU0NDQ0","body":"`beta.12` brings in the ability to start/stop background tasks.\r\n\r\n\r\nUsing `// Background :true` at the top of your script will change the behavior in the main menu:\r\n```js\r\n// Background: true\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## Auto (like nodemon)\r\n```js\r\n// Background: auto\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\nUsing `auto`, after you start the script, editing will stop/restart the script.\r\n","value":"https://github.com/johnlindquist/kit/discussions/245","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/213","url":"https://github.com/johnlindquist/kit/discussions/213","title":"// Watch: metadata 👀","name":"// Watch: metadata 👀","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/213","createdAt":"2021-04-29T14:31:31Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzQzODM2","body":"Script Kit now supports `// Watch:` metadata\r\n\r\n```js\r\n// Watch: ~/projects/thoughts/**/*.md\r\n\r\nlet { say } = await kit(\"speech\")\r\n\r\nsay(\"journal updated\")\r\n```\r\n\r\n* `// Watch: ` supports any file name, glob, or array (Kit will `JSON.parse` the array).\r\n* Scripts will run on the \"change\" event\r\n* Read more about supported [globbing](https://github.com/micromatch/picomatch#globbing-features)\r\n\r\n> Read about the [other metadata](https://github.com/johnlindquist/kit/discussions/185)\r\n\r\nI would _LOVE_ to hear about scenarios you would use this for or if you run into any issues 🙏","value":"https://github.com/johnlindquist/kit/discussions/213","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/150","url":"https://github.com/johnlindquist/kit/discussions/150","title":"beta.96 - Design, Drop, and Hotkeys! Oh my!","name":"beta.96 - Design, Drop, and Hotkeys! Oh my!","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/150","createdAt":"2021-04-16T20:32:44Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzI1MDcy","body":"\r\nhttps://user-images.githubusercontent.com/36073/115079813-fc5f2200-9ebe-11eb-8e7c-74c8a1d2aee3.mp4\r\n\r\nCan't wait to see what you build! Happy Scripting this weekend! 😇","value":"https://github.com/johnlindquist/kit/discussions/150","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/119","url":"https://github.com/johnlindquist/kit/discussions/119","title":"*New* Choice Preview","name":"*New* Choice Preview","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/119","createdAt":"2021-04-09T21:43:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzE0MTM5","body":"\r\nhttps://user-images.githubusercontent.com/36073/114220248-fc907800-9928-11eb-8096-61a5debbdc0d.mp4\r\n\r\n\r\n[Install google-image-search](https://scriptkit.app/api/new?name=google-image-search&url=https://gist.githubusercontent.com/johnlindquist/99756d4e1a54c737dc534c4edb5f6c9d/raw/55c440503a8a653c3ef3dafb9ba1bd567fc0b14a/google-image-search.js)\r\n\r\n```js\r\n// Menu: Google Image Search\r\n// Description: Searches Google Images\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nlet gis = await npm(\"g-i-s\")\r\n\r\nlet selectedImageUrl = await arg(\r\n \"Image search:\",\r\n async input => {\r\n if (input.length < 3) return []\r\n\r\n let searchResults = await new Promise(res => {\r\n gis(input, (_, results) => {\r\n res(results)\r\n })\r\n })\r\n\r\n return searchResults.map(({ url }) => {\r\n return {\r\n name: url.split(\"/\").pop().replace(/\\?.*/g, \"\"),\r\n value: url,\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\ncopy(selectedImageUrl)\r\n\r\n```\r\n\r\n\r\n\r\n[Install giphy-search](https://scriptkit.app/api/new?name=giphy-search&url=https://gist.githubusercontent.com/johnlindquist/dc17a3f07fb41b855e742a0f995cb0ed/raw/109831f9d40a8293b7d8741b44081fddcb024cda/giphy-search.js)\r\n\r\n```js\r\n// Menu: Giphy\r\n// Description: Search giphy. Paste markdown link.\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\nlet download = await npm(\"image-downloader\")\r\nlet queryString = await npm(\"query-string\")\r\nlet { setSelectedText } = await kit(\"text\")\r\n\r\nif (!env.GIPHY_API_KEY) {\r\n show(\r\n `
\r\n
\r\n Grab an API Key from the Giphy dev dashboard:\r\n
`\r\n )\r\n}\r\nlet GIPHY_API_KEY = await env(\"GIPHY_API_KEY\")\r\n\r\nlet search = q =>\r\n `https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${q}&limit=10&offset=0&rating=g&lang=en`\r\n\r\nlet { input, url } = await arg(\r\n \"Search giphy:\",\r\n async input => {\r\n if (!input) return []\r\n let query = search(input)\r\n let { data } = await get(query)\r\n\r\n return data.data.map(gif => {\r\n return {\r\n name: gif.title.trim() || gif.slug,\r\n value: {\r\n input,\r\n url: gif.images.downsized_medium.url,\r\n },\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\nlet formattedLink = await arg(\"Format to paste\", [\r\n {\r\n name: \"URL Only\",\r\n value: url,\r\n },\r\n {\r\n name: \"Markdown Image Link\",\r\n value: `![${input}](${url})`,\r\n },\r\n {\r\n name: \"HTML \",\r\n value: ``,\r\n },\r\n])\r\n\r\nsetSelectedText(formattedLink)\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/119","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/112","url":"https://github.com/johnlindquist/kit/discussions/112","title":"Types are here!","name":"Types are here!","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/112","createdAt":"2021-04-03T18:08:24Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzA1MzEx","body":"Update (1.1.0-beta.86) adds a [`~/.kit/kit.d.ts`](https://github.com/johnlindquist/kit/blob/main/kit.d.ts) to allow better code hinting and completion.\r\n\r\n❗️After updating, you will need to manually \"link\" your `~/.kenv` to your `~/.kit` for the benefits (This will happen automatically for new users during install)\r\n\r\nMethod 1 - Install and run this script\r\n\r\n[Click to install link-kit](https://scriptkit.app/api/new?name=link-kit&url=https://gist.githubusercontent.com/johnlindquist/f238cb1b3a3ed97890657ccf154d12b1/raw/a488a8b6c331d527bb0433a6b8df9428263b85a0/link-kit.js)\r\n\r\n```js\r\nawait cli(\"install\", \"~/.kit\")\r\n```\r\n\r\nMethod 2 - In your terminal\r\n```bash\r\nPATH=~/.kit/node/bin ~/.kit/node/bin/npm --prefix ~/.kenv i ~/.kit\r\n```\r\n\r\nNow your scripts in your `~/.kenv/scripts` should have completion/hinting for globals included in the \"preloaded\" scripts.\r\n\r\n> I still need to add types for the helpers that load scripts from dirs `kit()`, `cli()`, etc.\r\n\r\nPlease let me know how it goes and if you have any questions. Thanks!","value":"https://github.com/johnlindquist/kit/discussions/112","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"}]
+[{"avatar":"https://avatars.githubusercontent.com/u/52321532?u=ddb1a0825917a338122fd15542276e3b29f2f4af&v=4","user":"awakenedhaggis","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1390","url":"https://github.com/johnlindquist/kit/discussions/1390","title":"Query Kagi FastGPT","name":"Query Kagi FastGPT","extension":".md","description":"Created by awakenedhaggis","resourcePath":"/johnlindquist/kit/discussions/1390","createdAt":"2023-12-04T23:37:23Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AWmdW","body":"Wanted to make something similar to the OpenAi integrations already made for people who are subscribed to Kagi\r\n\r\n[Gist here](https://gist.github.com/awakenedhaggis/bd9dbf2421325117f7e5c20f62e1c99f)\r\n\r\n`Enter` to submit a query\r\n`Ctrl/Command + R` to rerun a query\r\n`Ctrl/Command + W` to close the window","value":"https://github.com/johnlindquist/kit/discussions/1390","img":"https://avatars.githubusercontent.com/u/52321532?u=ddb1a0825917a338122fd15542276e3b29f2f4af&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/3072458?u=cb1f8ec2b30c2c745c8f33c8225dee605b5ceab1&v=4","user":"AquiGorka","author":"Gorka Ludlow","twitter":"AquiGorka","discussion":"https://github.com/johnlindquist/kit/discussions/1388","url":"https://github.com/johnlindquist/kit/discussions/1388","title":"Keyboard shortcut to switch audio output to speakers","name":"Keyboard shortcut to switch audio output to speakers","extension":".md","description":"Created by AquiGorka","resourcePath":"/johnlindquist/kit/discussions/1388","createdAt":"2023-12-04T17:37:52Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AWmOQ","body":"Existing alias: `alias smac=\"SwitchAudioSource -s \\\"MacBook Pro Speakers\\\"\"` (uses [switchaudio-osx](https://github.com/deweller/switchaudio-osx))\r\n\r\nScript:\r\n\r\n```\r\n// Name: Shortcut to Speakers\r\n// Shortcut: cmd shift m\r\nimport \"@johnlindquist/kit\"\r\nawait $`/bin/zsh -lic smac`\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1388","img":"https://avatars.githubusercontent.com/u/3072458?u=cb1f8ec2b30c2c745c8f33c8225dee605b5ceab1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/3072458?u=cb1f8ec2b30c2c745c8f33c8225dee605b5ceab1&v=4","user":"AquiGorka","author":"Gorka Ludlow","twitter":"AquiGorka","discussion":"https://github.com/johnlindquist/kit/discussions/1387","url":"https://github.com/johnlindquist/kit/discussions/1387","title":"Keyboard shortcut to switch audio output to headphones","name":"Keyboard shortcut to switch audio output to headphones","extension":".md","description":"Created by AquiGorka","resourcePath":"/johnlindquist/kit/discussions/1387","createdAt":"2023-12-04T17:37:11Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AWmOO","body":"Existing alias: `alias shead=\"SwitchAudioSource -s \\\"External Headphones\\\"\"` (uses [switchaudio-osx](https://github.com/deweller/switchaudio-osx))\r\n\r\nScript:\r\n```\r\n// Name: Shortcut to Headphones\r\n// Shortcut: cmd shift h\r\nimport \"@johnlindquist/kit\"\r\nawait $`/bin/zsh -lic shead`\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1387","img":"https://avatars.githubusercontent.com/u/3072458?u=cb1f8ec2b30c2c745c8f33c8225dee605b5ceab1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/80865148?u=3b4757858e6abb48bc4fff9a133be4f009deaafe&v=4","user":"NGH14","author":"Nghia Vu","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1373","url":"https://github.com/johnlindquist/kit/discussions/1373","title":"Generate Dummy Data","name":"Generate Dummy Data","extension":".md","description":"Created by NGH14","resourcePath":"/johnlindquist/kit/discussions/1373","createdAt":"2023-11-06T17:06:44Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AWM0q","body":"[Install dummy-data](https://scriptkit.com/api/new?name=dummy-data&url=https://gist.githubusercontent.com/NGH14/b2efa176296362f26732bdc4fcf69402/raw/0d3ebc2ba760c720419cbd7399ed8dde62f0ca81/dummy-data.js)\r\n\r\n\r\n```js\r\n// Name: Dummy Data\r\n// Description: Generate fake data for real use-case (that #version just generate fields about person and color)\r\n// Author: Nghia Vu (Ngh14)\r\n\r\nimport '@johnlindquist/kit';\r\nimport falso from '@ngneat/falso';\r\n\r\nlet counts = 1;\r\n\r\nconst randZodiacSign = () => {\r\n\tconst zodiacSigns = [\r\n\t\t'Aries',\r\n\t\t'Taurus',\r\n\t\t'Gemini',\r\n\t\t'Cancer',\r\n\t\t'Leo',\r\n\t\t'Virgo',\r\n\t\t'Libra',\r\n\t\t'Scorpio',\r\n\t\t'Sagittarius',\r\n\t\t'Capricorn',\r\n\t\t'Aquarius',\r\n\t\t'Pisces',\r\n\t];\r\n\treturn falso.rand(zodiacSigns);\r\n};\r\n\r\nconst person = [\r\n\t{\r\n\t\tname: 'Full Name',\r\n\t\tvalue: () => falso.randFullName(),\r\n\t\tdescription: 'The complete name of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Title',\r\n\t\tvalue: () => falso.randPersonTitle(),\r\n\t\tdescription: 'The professional title of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Email',\r\n\t\tvalue: () => falso.randEmail(),\r\n\t\tdescription: 'The email address of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Phone',\r\n\t\tvalue: () => falso.randPhoneNumber(),\r\n\t\tdescription: 'The phone number of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Address',\r\n\t\tvalue: () => falso.randAddress(),\r\n\t\tdescription: 'The physical address of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Username',\r\n\t\tvalue: () => falso.randUserName(),\r\n\t\tdescription: 'The username chosen for the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Last Name',\r\n\t\tvalue: () => falso.randLastName(),\r\n\t\tdescription: 'The last name of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'First_Name',\r\n\t\tvalue: () => falso.randFirstName(),\r\n\t\tdescription: 'The first name of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Gender',\r\n\t\tvalue: () => falso.randGender(),\r\n\t\tdescription: 'The gender of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Avatar',\r\n\t\tvalue: () => falso.randAvatar(),\r\n\t\tdescription: 'The profile picture (avatar) of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Pronoun',\r\n\t\tvalue: () => falso.randPronoun(),\r\n\t\tdescription: 'The preferred pronoun of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Skill',\r\n\t\tvalue: () => falso.randSkill(),\r\n\t\tdescription: 'A skill associated with the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Password',\r\n\t\tvalue: () => falso.randPassword(),\r\n\t\tdescription: 'The password for the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Country',\r\n\t\tvalue: () => falso.randCountry(),\r\n\t\tdescription: 'The country of residence of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'City',\r\n\t\tvalue: () => falso.randCity(),\r\n\t\tdescription: 'The city of residence of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Role',\r\n\t\tvalue: () => falso.randRole(),\r\n\t\tdescription: 'The role or position held by the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Company',\r\n\t\tvalue: () => falso.randCompanyName(),\r\n\t\tdescription:\r\n\t\t\t'The name of the company associated with the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Subscription Plan',\r\n\t\tvalue: () => falso.randSubscriptionPlan(),\r\n\t\tdescription: 'The subscription plan chosen by the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Date of Birth',\r\n\t\tvalue: () => falso.randPastDate(),\r\n\t\tdescription: 'The date of birth of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Credit Card',\r\n\t\tvalue: () => falso.randCreditCard(),\r\n\t\tdescription: 'The randomly generated credit card information of the user.',\r\n\t},\r\n\t{\r\n\t\tname: 'User Agent',\r\n\t\tvalue: () => falso.randUserAgent(),\r\n\t\tdescription:\r\n\t\t\t'The user agent of the device used by the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'IP',\r\n\t\tvalue: () => falso.randIP(),\r\n\t\tdescription: 'The randomly generated IP address of the user.',\r\n\t},\r\n\t{\r\n\t\tname: 'IP6',\r\n\t\tvalue: () => falso.randIP6(),\r\n\t\tdescription: 'The randomly generated IPv6 address of the user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Zodiac Sign',\r\n\t\tvalue: () => randZodiacSign(),\r\n\t\tdescription: 'The randomly generated zodiac sign of the user.',\r\n\t},\r\n];\r\n\r\nconst color = [\r\n\t{\r\n\t\tname: 'Name',\r\n\t\tvalue: () => falso.randColor(),\r\n\t\tdescription: 'Generates a random color name.',\r\n\t},\r\n\t{\r\n\t\tname: 'HEX',\r\n\t\tvalue: () => falso.randHex(),\r\n\t\tdescription: 'Generates a random hexadecimal color code.',\r\n\t},\r\n\t{\r\n\t\tname: 'HSL',\r\n\t\tvalue: () => falso.randHsl(),\r\n\t\tdescription:\r\n\t\t\t'Generates a random HSL (Hue, Saturation, Lightness) color code.',\r\n\t},\r\n\t{\r\n\t\tname: 'RGB',\r\n\t\tvalue: () => falso.randRgb(),\r\n\t\tdescription: 'Generates a random RGB (Red, Green, Blue) color code.',\r\n\t},\r\n\t{\r\n\t\tname: 'RGBa',\r\n\t\tvalue: () => falso.randRgb({ alpha: true }),\r\n\t\tdescription:\r\n\t\t\t'Generates a random RGBA (Red, Green, Blue, Alpha) color code.',\r\n\t},\r\n];\r\n\r\nfunction filterDataField(arr, field) {\r\n\tconst filtered = {};\r\n\tarr.map(({ name, value }) => {\r\n\t\tif (field.includes(name)) {\r\n\t\t\tfiltered[name] = value();\r\n\t\t}\r\n\t});\r\n\treturn filtered;\r\n}\r\n\r\nfunction generateData({ types, fields, ...rest }) {\r\n\tconst arr = [];\r\n\r\n\tfor (let index = 0; index < counts; index++) {\r\n\t\tarr.push(filterDataField(types, fields));\r\n\t}\r\n\tsetSelectedText(JSON.stringify(arr));\r\n}\r\n\r\nlet types = await arg('Generate Random Data...', ['people', 'color']);\r\n\r\nif (types == 'people') {\r\n\tlet peopleField = await select(\r\n\t\t'Select a fields....',\r\n\t\tperson\r\n\t\t\t.sort((a, b) => a.name.localeCompare(b.name))\r\n\t\t\t.map(({ name, value, description }) => ({\r\n\t\t\t\tname,\r\n\t\t\t\tdescription,\r\n\t\t\t\tvalue: name,\r\n\t\t\t\theight: PROMPT.HEIGHT.XS,\r\n\t\t\t\tpreview: () => JSON.stringify(value()),\r\n\t\t\t})),\r\n\t);\r\n\r\n\tcounts = await arg({\r\n\t\tdescription: 'How many person records you want?',\r\n\t\tplaceholder: '1',\r\n\t});\r\n\r\n\tgenerateData({ types: person, fields: peopleField });\r\n} else if (types == 'color') {\r\n\tlet result = await arg(\r\n\t\t'Generate random color...',\r\n\t\tcolor.map(({ name, value, description }) => ({\r\n\t\t\tname,\r\n\t\t\tdescription,\r\n\t\t\tvalue: value(),\r\n\t\t\theight: PROMPT.HEIGHT.XS,\r\n\t\t\tpreview: () => value(),\r\n\t\t})),\r\n\t);\r\n\r\n\tsetSelectedText(result);\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1373","img":"https://avatars.githubusercontent.com/u/80865148?u=3b4757858e6abb48bc4fff9a133be4f009deaafe&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/73789?v=4","user":"ndrake","author":"Nate Drake","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1367","url":"https://github.com/johnlindquist/kit/discussions/1367","title":"Audio Output Switcher","name":"Audio Output Switcher","extension":".md","description":"Created by ndrake","resourcePath":"/johnlindquist/kit/discussions/1367","createdAt":"2023-11-01T01:28:45Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AWH9z","body":"A Mac-only script using the [switchaudio-osx](https://github.com/deweller/switchaudio-osx) tool to quickly change audio output devices\r\n\r\n[Open switch-audio in Script Kit](https://scriptkit.com/api/new?name=switch-audio&url=https://gist.githubusercontent.com/ndrake/3c0840f03662a21900a81ec22ab734a1/raw/81b7c4c7538ca93eca953a6c34e58651f528fddf/switch-audio.js\")\r\n\r\n```js\r\n/*\r\n## Switch audio output device\r\n*/\r\n\r\n// Name: Switch Audio\r\n// Description: Switch audio output device (Mac only)\r\n// Author: Nate Drake\r\n\r\n// Install SwitchAudioSource with `brew install switchaudio-osx`\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst SwitchAudioSourcePath = '/opt/homebrew/bin/SwitchAudioSource'\r\n\r\nconst sasExists = await pathExists(SwitchAudioSourcePath)\r\n\r\nif (sasExists) {\r\n\r\n const currentOutput = await $`${SwitchAudioSourcePath} -c`\r\n const items = await $`${SwitchAudioSourcePath} -a -t output`\r\n\r\n const choices = items.stdout.trim().split(/\\r?\\n/).filter(o => o !== currentOutput.stdout.trim())\r\n\r\n let output = await arg(\r\n {\r\n placeholder: 'Pick ouput device'\r\n },\r\n choices\r\n )\r\n\r\n await $`${SwitchAudioSourcePath} -s ${output} -t output`\r\n} else {\r\n await div(md(`ERROR: Please install SwitchAudioSource`))\r\n}\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1367","img":"https://avatars.githubusercontent.com/u/73789?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1365","url":"https://github.com/johnlindquist/kit/discussions/1365","title":"Script Kit 2.0 Release Candidate","name":"Script Kit 2.0 Release Candidate","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1365","createdAt":"2023-10-27T07:04:16Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AWD5A","body":" # Script Kit 2.0 Release Candidate\r\n\r\nDownload for your platform from the releases page:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.99.82\r\n\r\n## New Main Menu\r\n\r\nThe main menu has been re-designed and optimized for your keyboard-centric workflows.\r\n\r\n\r\n\r\n## New Custom OSX Window\r\n\r\nPrompts like `editor` and `term` which use `ignoreBlur` will now convert the window into an \"app\" window which can be accessed by `cmd+tab`, is pinned when switching spaces, and shows up in Mission Control. This is also forward-looking to v3 which I'll talk about more in the future...\r\n\r\n## Keywords\r\nBorrowed directly from Alfred, keywords allow you to trigger a script by typing a keyword followed by a space.\r\n\r\nA keyword can be a single character or a word. For example, `c` for `clipboard`.\r\n\r\n### Built-in Keywords\r\n- `s` - Displays Snippets from your Snippets Directory\r\n- `c` - Displays Clipboard History\r\n- `f` - Find script by searching script contents\r\n- `kit` - Access kit settings\r\n- `kenv` - Access kenv options\r\n- `npm` - Add/remove npm packages\r\n- `spell` - List spelling suggestions for the input\r\n \r\n And many others. To see them all, type `keyword` into the Script Kit prompt.\r\n\r\n To create a custom keyword for your own scripts, add:\r\n\r\n ```\r\n // Keyword: mycustomkeyword\r\n ```\r\n\r\n If the first prompt is an `await arg()`, it will display the list in the main menu. Otherwise, it will jump to the next prompt.\r\n\r\n Also, if the first prompt is a static list and you shave off the few milliseconds required to load the list from your script, you can cache the list (after the first run) by adding:\r\n\r\n ```\r\n // Cache: true\r\n ```\r\n\r\nUse caching sparingly, as it's only useful for `// Keyword` and `// Shortcut` where the first prompt is an `arg` with a static list.\r\n\r\n## Pass Main Menu Input to a Script\r\n\r\n```\r\n// Pass: true\r\n```\r\n\r\nThe `Pass` metadata allows you to type into the main menu without your script being filtered out of the list. Once you select the script, the input you've typed will be \"passed\" into the script. You can access the \"passed\" input using `flag?.pass`. For example, if you want to pass the main menu input to the input of you first arg:\r\n\r\n```\r\n// Name: Pass Demo\r\n// Pass: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg({\r\n input: flag?.pass || \"\",\r\n})\r\n```\r\n\r\n### Pass \"postfix\"\r\n\r\nImage you want to type: \"How many moons does Jupiter have?\" and have the \"?\" automatically trigger a script that reaches out to an AI service. You can do this by adding a \"postfix\" to the `Pass` metadata.\r\n\r\n```\r\n// Pass: ?\r\n```\r\n\r\nThis will automatically trigger the script when you type a \"?\" at the end in the main menu and the entire input will be passed to the script as `flag?.pass`.\r\n\r\n## Multi-select Prompt `await select()`\r\n\r\n\r\n\r\n```js\r\nlet arrayOfChoices = await select(\"Pick one or more\", [\"one\", \"two\", \"three\"])\r\n```\r\n\r\nProbably the most requested feature, Script Kit now has a `select` prompt which allows you to pick multiple choices.\r\n\r\n## Themes Per Light/Dark Mode\r\n\r\nYou can now customize which theme will be active for both light and dark more or use the same theme for both.\r\n\r\n## Snippets Directory\r\n\r\nIn your ~/.kenv/snippets directory, create .txt files you want to use as snippets. For example:\r\n1. Create `~/.kenv/snippets/lorem.txt`\r\n2. Add the following text to the file:\r\n```\r\n// Name: Lorem Ipsum\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\r\n```\r\n\r\nThe \"Lorem Ipsum\" snippet will now appear in the Snippets menu using the `s` keyword. Select the snippet will paste it into the current application.\r\n\r\nAdding Snippet metadata will allow you to invoke the snippet anywhere on your system. It's a best practice to use a \"postfix\", such as \",,\", to avoid triggering the snippet when you don't want to.\r\n```\r\n// Snippet: lorem,,\r\n```\r\n\r\n### Snippet Creator\r\n\r\n\r\n\r\nTyping `ns ` from the main menu will launch the quick snippet creator. There's a helpful guide there that will show all the available options. Just to list a few:\r\n\r\n- Tab stops: $0, $1, $2, $3\r\n- Dropdown list: ${|apple,banana,cherry|}\r\n- Current selection: $SELECTION\r\n- Clipboard content: $CLIPBOARD\r\n\r\nand many more...\r\n\r\nIf you need more advanced scenarios, for example clipboard history:\r\n\r\n```js\r\nlet history = await getClipboardHistory()\r\nlet text = await arg(\"Pick an item from your history\", history.map(item => item.value)\r\n\r\nlet result = await template(`Hello $0,\r\n\r\n${text}\r\n`)\r\n\r\nawait writeFile(home(\"example.txt\"), result)\r\n```\r\n\r\n## Actions\r\n\r\nThe new `actions` array makes it much easier to assign shortcuts and behaviors when you have a choice selected. You can access the actions menu by pressing `right`on the keyboard, then quickly search to execute the action you want to take with the currently focused choice:\r\n\r\n```js\r\n// Name: Actions Example\r\n\r\nimport { Action } from \"@johnlindquist/kit\"\r\n\r\n// Using a \"flag\" determines where to do to custom logic: After the prompt or in the action\r\n// Also, \"flags\" are supported when running the script in the terminal with `--js`\r\nlet actions: Action[] = [\r\n {\r\n shortcut: `${cmd}+t`,\r\n name: \"Append .ts\",\r\n visible: true, // Display shortcut in the prompt\r\n onAction: async (input, state) => {\r\n // Since we're not using a \"flag\", we can do custom logic here\r\n submit(`${state.focused?.name}.ts`)\r\n },\r\n },\r\n {\r\n name: \"Append .js\",\r\n flag: \"js\", // Set `global.flag.js` to true when selecting the action\r\n },\r\n]\r\n\r\nlet result = await arg(\"Pick a number\", [\"one\", \"two\", \"three\"], actions)\r\n\r\nif (flag.js) {\r\n result = `${result}.js`\r\n}\r\n\r\ninspect(result)\r\n```\r\n\r\n## Previews on All Prompts\r\n\r\nYou can build-out helpful previews on the right side for all prompts now (see the Snippet Creator above for a great example). This is especially useful for prompts like `editor`, `drop`, and `term` where you may need reminders on what the script is doing or steps you want to guide the user through.\r\n\r\n```js\r\nawait editor({\r\n value: `Book Outline`,\r\n preview: md(`# Requirements\r\n- Cover Major Characters\r\n- Address the theme\r\n- Have a beginning, middle, and end \r\n `),\r\n})\r\n```\r\n\r\n\r\n## Move kenv to a Different Directory\r\n\r\nType `kit move` into the main prompt to move your kenv directory to a different location. This is useful if you want to keep your scripts in dotfiles or other setup.\r\n\r\n\r\n## onClick and onKeydown Globals\r\n- Global event handlers for click and type events.\r\n\r\n## Custom TypeScript Loader\r\n\r\nThe new custom loader eliminates the need to watch .ts files for changes (which caused many issues from a long-running app). We now have a customized loader that can run any TypeScript file instantly.\r\n\r\nAn awesome benefit of this is you can create a script anywhere on your system, such as `~/Desktop/my-script.ts` (or js if you prefer) and run it from script runner: `~/.kit/bin/kit ~/Desktop/my-script.ts`.\r\n\r\n> Note: Your IDE won't recognize the `import` statements since it's not in a project, but the script will run just fine.\r\n\r\nIf we get enough requests, we will allow you to run the scripts from the built-in file browser through the app as well.\r\n\r\n\r\n## Watch Scripts for npm Install\r\n\r\nAs you're writing your scripts in your favorite editor, Kit.app will detect if you've added a new npm package and automatically prompt you to install it using the Kit.app built-in terminal.\r\n\r\n\r\n## Road to v2\r\n\r\nThe plan is to release v2 in mid-December. I'll be focusing on updating documentation, guides, tips, and tricks to finally help everyone get a more complete grasp of what Script Kit is capable of. There are a few more APIs which haven't been announced which will be included as well.\r\n\r\n\r\n## Questions?\r\n\r\nPlease ask below. We're always happy to help figure out better workflows to optimize your day. Happy scripting!\r\n\r\n\\- John","value":"https://github.com/johnlindquist/kit/discussions/1365","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/12806880?u=dd4d009aa8af0e17149a51a18969d29d1e4fd8de&v=4","user":"aldirrix","author":"Aldo Preciado","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1360","url":"https://github.com/johnlindquist/kit/discussions/1360","title":"Clipboard history with image preview","name":"Clipboard history with image preview","extension":".md","description":"Created by aldirrix","resourcePath":"/johnlindquist/kit/discussions/1360","createdAt":"2023-10-16T08:25:15Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AV5tI","body":"Hello there, first of all I would like to praise everyone that made this possible so far! I really like using kit and tweaking few scripts here and there for productivity.\r\n\r\nI was using an adapted version of the [community-available clipboard history](https://github.com/johnlindquist/kit/discussions/1120) but I was struggling with some errors regarding the database from time to time and this was also causing processes not being finished properly and running forever and forcing me to restart kit. The db file was also randomly deleted at times and other not so nice things that were basically making me having to come back from time to time.\r\n\r\n```\r\n[2023-05-22 08:51:17.374] [warn] ☠️ ERROR PROMPT SHOULD SHOW ☠️\r\n[2023-05-22 08:51:17.393] [warn] Error: ENOENT: no such file or directory, rename '/Users/aldo/.kenv/db/.clipboard-history.json.tmp' -> '/Users/aldo/.kenv/db/clipboard-history.json'\r\n```\r\n\r\nAfter dealing with it for few months, I realized we now have the `getClipboardHistory` function when the watcher is enabled so I'd like to share this with everyone so that we have a proper history paste with image preview that doesn't require the managing of a json file.\r\n\r\n```ts\r\n// Name: Clipboard history\r\n// Author: Aldo Preciado\r\n// GitHub: @aldirrix\r\n// Shortcut: command shift v\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst history = await getClipboardHistory();\r\n\r\nlet { value, type } = await arg(\"What to paste?\", () => {\r\n return history.map(({ value, type, timestamp, maybeSecret }) => {\r\n const multilinePreview = value.includes(\"\\n\")\r\n ? `
${value\r\n .split(\"\\n\")\r\n .map((line) => `
${line}
`)\r\n .join(\"\")}
`\r\n : null;\r\n\r\n const preview = type === \"image\" ? `` : multilinePreview;\r\n\r\n return {\r\n type,\r\n name: maybeSecret ? value.slice(0, 2).padEnd(10, \"*\") : value,\r\n value: {\r\n value,\r\n type,\r\n },\r\n description: timestamp,\r\n preview,\r\n };\r\n });\r\n});\r\n\r\nif (type === \"text\") {\r\n await setSelectedText(value);\r\n}\r\n\r\nif (type === \"image\") {\r\n await copyPathAsImage(value);\r\n await keystroke(\"command v\");\r\n}\r\n```\r\n\r\nOn the same note, is there a way for us to configure the `maybeSecret` property in the kit app? It seems like there has not been any discussion around it and I was wondering","value":"https://github.com/johnlindquist/kit/discussions/1360","img":"https://avatars.githubusercontent.com/u/12806880?u=dd4d009aa8af0e17149a51a18969d29d1e4fd8de&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/25487857?u=8de0c1a02b8786762899a89fa547ddd29dc17c20&v=4","user":"vojtaholik","author":"Vojta Holik","twitter":"vojta_holik","discussion":"https://github.com/johnlindquist/kit/discussions/1357","url":"https://github.com/johnlindquist/kit/discussions/1357","title":"Screenshot tool alternative with Cloudinary","name":"Screenshot tool alternative with Cloudinary","extension":".md","description":"Created by vojtaholik","resourcePath":"/johnlindquist/kit/discussions/1357","createdAt":"2023-10-12T09:42:34Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AV2hY","body":"This is my alternative to _[insert your favorite screenshot upload tool]_. I use it in combination with [Record Screen script](https://github.com/johnlindquist/kit/discussions/1356).\r\n\r\nIt watches a `screenshots` directory on desktop and uploads any new file to Cloudinary. I have it then set to copy link to clipboard and move the file to trash. On top of that I have a dynamic route on my personal website to display the image/video along with a simple kvstore to track views (anonymously, just so that I know when someone view the file). It looks like [this](https://vojta.io/shots/1696935964744). Code for it is [here](https://github.com/vojtaholik/vojta-io-next/blob/main/src/pages/shots/%5Bpublic_id%5D.tsx).\r\n\r\nOne of nice things about Cloudinary is that if I record a video, all I have to do is replace`.mov` with `.gif` in url to get a gif.\r\n\r\nDon't forget to run following command in your terminal to change default screenshot (`cmd+shift+4`) location:\r\n```bash\r\ndefaults write com.apple.screencapture location ~/Desktop/screenshots\r\n``` \r\n\r\n[Open watch-screenshots in Script Kit](https://scriptkit.com/api/new?name=watch-screenshots&url=https://gist.githubusercontent.com/vojtaholik/3a7e5639544f2c62cbff989141f1da70/raw/c0782ea081d4afbe7bbd6e964d64f7f7b2b9fd54/watch-screenshots.js\")\r\n\r\n```js\r\n// Name: Watch Screenshots Dir\r\n// Watch: ~/Desktop/screenshots\r\n// Description: Don't forget to run following command in your terminal to set default screenshot directory in macOSX: defaults write com.apple.screencapture location ~/Desktop/screenshots\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport cloudinary from \"cloudinary\";\r\nimport trash from \"trash\";\r\n\r\nconst DIR = \"screenshots\";\r\nconst NOTIFY_SOUND_FILE_PATH = false; // home(\"Desktop/come-here-notification.mp3\");\r\nconst CUSTOM_DOMAIN = false; // 'https://vojta.io/shots/'\r\n\r\n// These are optional and automatically set by the watcher\r\nlet filePath = await arg();\r\nlet event = await arg();\r\n\r\n// Cloudinary options\r\nconst options = {\r\n public_id: `${DIR}/${Date.now()}`,\r\n unique_filename: true,\r\n use_filename: true,\r\n overwrite: true,\r\n filename_override: true,\r\n};\r\n\r\ncloudinary.v2.config({\r\n cloud_name: await env(\"CLOUDINARY_CLOUD_NAME\"),\r\n api_key: await env(\"CLOUDINARY_API_KEY\"),\r\n api_secret: await env(\"CLOUDINARY_API_SECRET\"),\r\n});\r\n\r\n// if file is added to DIR directory\r\nif (event === \"add\") {\r\n await appendFile(home(`Desktop/${DIR}/download.log`), filePath + \"\\n\");\r\n const isVideoFile = filePath.endsWith(\".mov\");\r\n\r\n await cloudinary.v2.uploader.upload(\r\n filePath,\r\n { ...options, resource_type: isVideoFile ? \"video\" : \"image\" },\r\n async (error, result) => {\r\n if (error) {\r\n console.error(\"Error uploading file:\", error);\r\n } else {\r\n if (result) {\r\n await copy(\r\n CUSTOM_DOMAIN\r\n ? `${CUSTOM_DOMAIN}${result.public_id.replace(`${DIR}/`, \"\")}`\r\n : isVideoFile\r\n ? result.url.replace(\".mov\", \".mp4\")\r\n : result.url\r\n );\r\n notify(\"✓ Uploaded to Cloudinary\");\r\n NOTIFY_SOUND_FILE_PATH &&\r\n (await playAudioFile(NOTIFY_SOUND_FILE_PATH));\r\n await trash([filePath]);\r\n }\r\n }\r\n }\r\n );\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1357","img":"https://avatars.githubusercontent.com/u/25487857?u=8de0c1a02b8786762899a89fa547ddd29dc17c20&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/25487857?u=8de0c1a02b8786762899a89fa547ddd29dc17c20&v=4","user":"vojtaholik","author":"Vojta Holik","twitter":"vojta_holik","discussion":"https://github.com/johnlindquist/kit/discussions/1356","url":"https://github.com/johnlindquist/kit/discussions/1356","title":"Record Screen","name":"Record Screen","extension":".md","description":"Created by vojtaholik","resourcePath":"/johnlindquist/kit/discussions/1356","createdAt":"2023-10-12T09:30:50Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AV2g3","body":"`shift + cmd + 5` will start a screen recording session on macOS Sonoma. It's got video trimming feature and is overall pretty good. I use it in combination with my [screenshot upload script](https://github.com/johnlindquist/kit/discussions/1357).\r\n\r\n[Open record-screen in Script Kit](https://scriptkit.com/api/new?name=record-screen&url=https://gist.githubusercontent.com/vojtaholik/ded540fc8b553751887adbc03abcca90/raw/973985e7ef57eff98d825229edfe5a00145e2978/record-screen.js\")\r\n\r\n```js\r\n// Menu: Record Screen\r\n// Shortcut: shift cmd 5\r\n\r\n/** @type {import(\"@johnlindquist/kit\")} */\r\n\r\nawait applescript(`\r\n-- # Setup to do a screen recording.\r\n\r\n# tell application \"QuickTime Player\" to new screen recording\r\n\r\n-- # Start the screen recording.\r\n\r\ntell application \"System Events\" to tell process \"Screen Shot\"\r\n repeat until exists button \"Record\" of its front window\r\n delay 0.1\r\n end repeat\r\n click button \"Record\" of its front window\r\nend tell\r\n\r\n-- # Set the time in seconds you want the recording to be.\r\n\r\ndelay 2\r\n\r\n-- # Stop the recording.\r\n\r\ntell application \"System Events\" to ¬\r\n click menu bar item 1 ¬\r\n of menu bar 1 ¬\r\n of application process \"screencaptureui\"\r\n`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1356","img":"https://avatars.githubusercontent.com/u/25487857?u=8de0c1a02b8786762899a89fa547ddd29dc17c20&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/150462?u=6478fa6d3285adcd99bf6819f9f7758f4de0d277&v=4","user":"kkoscielniak","author":"Krystian Kościelniak","twitter":"pankoscielniak","discussion":"https://github.com/johnlindquist/kit/discussions/1354","url":"https://github.com/johnlindquist/kit/discussions/1354","title":"Arc: Use default theme","name":"Arc: Use default theme","extension":".md","description":"Created by kkoscielniak","resourcePath":"/johnlindquist/kit/discussions/1354","createdAt":"2023-10-04T09:19:35Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AVvlH","body":"This script allows for choosing the Space in [Arc Browser](https://arc.net) and reuse the same theme for every other Space.\r\n\r\n> I've created it because I dislike the default theme in Arc but still want to have the same one for every Space I have. \r\n\r\n> Note: This script modifies Arc Browser's `StorableSidebar.json` file. Use at your peril.\r\n\r\n[Open arc-default-theme in Script Kit](https://scriptkit.com/api/new?name=arc-default-theme&url=https://gist.githubusercontent.com/kkoscielniak/524092e7812d37c1d30f0dc5aea5d0f8/raw/4b54a4dc63537e6e021766d7a03fdd98ebce03b6/arc-default-theme.ts\")\r\n\r\n```js\r\n// Name: arc-default-theme\r\n// Description: Pick an Arc Browser's Space and set its theme for all the other Arc Spaces. Tested with Arc v1.10.1.\r\n// Note: This script modifies Arc Browser's `StorableSidebar.json` file. Use at your peril.\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport { readdir, readFile, writeFile } from \"node:fs/promises\";\r\nimport { homedir } from \"node:os\";\r\nimport { join } from \"node:path\";\r\n\r\nconst { rimraf } = await npm(\"rimraf\");\r\nconst psList = await npm(\"ps-list\");\r\n\r\nconst ARC_LIBRARY_PATH = join(\r\n homedir(),\r\n \"Library\",\r\n \"Application Support\",\r\n \"Arc\"\r\n);\r\n\r\nasync function listSidebarCacheFiles(): Promise {\r\n const arcFileNames = await readdir(ARC_LIBRARY_PATH);\r\n\r\n return arcFileNames.filter(\r\n (file) =>\r\n file.startsWith(\"StorableSidebar\") && file !== \"StorableSidebar.json\"\r\n ) as string[];\r\n}\r\n\r\nasync function removeSidebarCacheFiles(): Promise {\r\n const sidebarCacheFileNames = await listSidebarCacheFiles();\r\n\r\n for (const fileName of sidebarCacheFileNames) {\r\n try {\r\n await rimraf(join(ARC_LIBRARY_PATH, fileName));\r\n } catch (err) {\r\n console.error(err);\r\n }\r\n }\r\n}\r\n\r\nasync function findArcProcess(): Promise<{\r\n name: string;\r\n pid: number;\r\n}> {\r\n const processes = await psList();\r\n\r\n const arcProcess = processes.find((process) => process.name === \"Arc\");\r\n\r\n return arcProcess;\r\n}\r\n\r\nasync function killArcProcess(): Promise {\r\n const arcProcess = await findArcProcess();\r\n\r\n if (arcProcess) {\r\n process.kill(arcProcess.pid);\r\n }\r\n}\r\n\r\nasync function readStorableSidebarJson(): Promise {\r\n const storableSidebarJson = await readFile(\r\n join(ARC_LIBRARY_PATH, \"StorableSidebar.json\"),\r\n \"utf-8\"\r\n );\r\n\r\n return JSON.parse(storableSidebarJson) as StorableSidebarJson;\r\n}\r\n\r\nasync function getSourceSpaceTheme(\r\n json: StorableSidebarJson,\r\n sourceSpaceName: string\r\n): Promise {\r\n const sourceSpace: SpaceModel = json.sidebarSyncState.spaceModels.find(\r\n (spaceModel) =>\r\n typeof spaceModel !== \"string\" &&\r\n spaceModel.value?.title === sourceSpaceName\r\n ) as SpaceModel;\r\n\r\n return sourceSpace.value?.customInfo.windowTheme;\r\n}\r\n\r\nasync function getTargetSpaces(\r\n json: StorableSidebarJson,\r\n originalSpaceName: string\r\n): Promise {\r\n const itemsContainer = json.sidebar.containers.find((container) =>\r\n Object.hasOwnProperty.call(container, \"items\")\r\n );\r\n\r\n if (itemsContainer) {\r\n const spaces = itemsContainer.spaces as (string | SpaceData)[];\r\n\r\n return spaces.filter(\r\n (space) => typeof space !== \"string\" && space.title !== originalSpaceName\r\n ) as SpaceData[];\r\n }\r\n}\r\n\r\nasync function getTargetSpacesSynced(\r\n json: StorableSidebarJson,\r\n originalSpaceName: string\r\n): Promise {\r\n return json.sidebarSyncState.spaceModels.filter(\r\n (spaceModel) =>\r\n typeof spaceModel !== \"string\" &&\r\n spaceModel.value?.title !== originalSpaceName\r\n ) as SpaceModel[];\r\n}\r\n\r\nasync function writeStorableSidebarJson(\r\n json: StorableSidebarJson\r\n): Promise {\r\n await removeSidebarCacheFiles();\r\n\r\n await writeFile(\r\n join(ARC_LIBRARY_PATH, \"StorableSidebar.json\"),\r\n JSON.stringify(json, null, 2)\r\n );\r\n}\r\n\r\nasync function mapJsonToSpaceNames(\r\n json: StorableSidebarJson\r\n): Promise {\r\n const itemsContainer = json.sidebar.containers.find((container) =>\r\n Object.hasOwnProperty.call(container, \"spaces\")\r\n );\r\n\r\n if (itemsContainer) {\r\n const spaces = itemsContainer.spaces as (string | SpaceData)[];\r\n\r\n return (\r\n spaces.filter((space) => typeof space !== \"string\") as SpaceData[]\r\n ).map((space) => space.title);\r\n }\r\n}\r\n\r\nasync function main(): Promise {\r\n await killArcProcess();\r\n await removeSidebarCacheFiles();\r\n\r\n const storableSidebarJson: StorableSidebarJson =\r\n await readStorableSidebarJson();\r\n\r\n const spaceNames = await mapJsonToSpaceNames(storableSidebarJson);\r\n\r\n const sourceSpaceName = await arg(\r\n \"Which Space theme you want to use for all the others?\",\r\n spaceNames\r\n );\r\n\r\n const sourceSpaceTheme: WindowTheme = await getSourceSpaceTheme(\r\n storableSidebarJson,\r\n sourceSpaceName\r\n );\r\n\r\n const targetSpaces: SpaceData[] = await getTargetSpaces(\r\n storableSidebarJson,\r\n sourceSpaceName\r\n );\r\n\r\n for (const targetSpace of targetSpaces) {\r\n targetSpace.customInfo.windowTheme = sourceSpaceTheme;\r\n }\r\n\r\n const targetSpacesSynced: SpaceModel[] = await getTargetSpacesSynced(\r\n storableSidebarJson,\r\n sourceSpaceName\r\n );\r\n\r\n for (const targetSpace of targetSpacesSynced) {\r\n targetSpace.value.customInfo.windowTheme = sourceSpaceTheme;\r\n }\r\n\r\n writeStorableSidebarJson(storableSidebarJson);\r\n}\r\n\r\nawait main();\r\n\r\n// Interfaces ------------------------------------------------------------------\r\n\r\ninterface Color {\r\n colorSpace: string;\r\n red: number;\r\n alpha: number;\r\n blue: number;\r\n green: number;\r\n}\r\n\r\ninterface ColorSettings {\r\n [key: string]: Color;\r\n}\r\n\r\ninterface WindowTheme {\r\n semanticColorPalette: {\r\n appearanceBased: {\r\n light: ColorSettings;\r\n dark: ColorSettings;\r\n };\r\n };\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface CustomInfo {\r\n windowTheme: WindowTheme;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SpaceData {\r\n title: string;\r\n customInfo: CustomInfo;\r\n id: string;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SpaceModel {\r\n value: SpaceData;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SidebarSyncState {\r\n spaceModels: (string | SpaceModel)[];\r\n}\r\n\r\ninterface Sidebar {\r\n containers: Array<\r\n | {\r\n spaces: (SpaceData | string)[];\r\n }\r\n | {\r\n [key: string]: unknown;\r\n }\r\n >;\r\n}\r\n\r\ninterface StorableSidebarJson {\r\n sidebarSyncState: SidebarSyncState;\r\n sidebar: Sidebar;\r\n [key: string]: unknown;\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1354","img":"https://avatars.githubusercontent.com/u/150462?u=6478fa6d3285adcd99bf6819f9f7758f4de0d277&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1345","url":"https://github.com/johnlindquist/kit/discussions/1345","title":"generate-password","name":"generate-password","extension":".md","description":"Created by abernier","resourcePath":"/johnlindquist/kit/discussions/1345","createdAt":"2023-09-19T21:37:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AVjJe","body":"[`Generate-password`](https://www.npmjs.com/package/generate-password)s variants:\r\n\r\n\r\n\r\nCreate a [`~/.generatepasswordrc` file](https://www.npmjs.com/package/rc#standards) to change [defaults](https://www.npmjs.com/package/generate-password#user-content-available-options):\r\n\r\n```ini\r\nlength=12\r\nnumbers=true\r\nsymbols=true\r\n```\r\n\r\nSee source: https://gist.github.com/abernier/3dcf17422b23e151c2f60db874494233\r\n\r\n```ts\r\n// Generate password\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nimport { generate, generateMultiple, GenerateOptions } from \"generate-password\";\r\nimport rc from \"rc\";\r\n\r\nimport omit from \"lodash.omit\";\r\n\r\nimport { passwordStrength } from \"check-password-strength\";\r\n\r\nconst config = rc(\"generatepassword\", { length: 10 }); // create a ~/.generate-passwordrc file (see: https://www.npmjs.com/package/rc#standards)\r\n\r\nconst flags = {\r\n copy: {\r\n name: \"copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n};\r\n\r\nfunction formatVariant(o) {\r\n if (Object.keys(o).length === 0) o = config;\r\n\r\n const arr = [];\r\n Object.keys(o).forEach((k) => {\r\n if (o[k] === true) {\r\n arr.push(`--${k}`);\r\n } else if (o[k] === false) {\r\n arr.push(`--no-${k}`);\r\n }\r\n });\r\n return arr.join(\" \");\r\n}\r\n\r\nfunction sortedObj(o) {\r\n return Object.fromEntries(Object.entries(o).sort());\r\n}\r\n\r\nconst chosenPass = await arg(\r\n {\r\n placeholder: (config.length && String(config.length)) || undefined,\r\n description: `Length`,\r\n flags,\r\n },\r\n (input) => {\r\n const baseOpts = { ...config };\r\n\r\n const length = (input && Number(input)) || undefined;\r\n if (length) baseOpts.length = length;\r\n\r\n const variants = [\r\n {},\r\n { numbers: true, symbols: true },\r\n {\r\n numbers: true,\r\n symbols: true,\r\n excludeSimilarCharacters: true,\r\n },\r\n { numbers: true, symbols: true, lowercase: false },\r\n { numbers: true, symbols: true, uppercase: false },\r\n { numbers: true },\r\n { symbols: true },\r\n { lowercase: false },\r\n { uppercase: false },\r\n ];\r\n\r\n return variants.map((variant) => {\r\n const opts = { ...baseOpts, ...variant };\r\n const newPass = generate(opts);\r\n\r\n const description =\r\n Object.keys(variant).length === 0\r\n ? `from config: ${config.config}`\r\n : formatVariant(variant);\r\n\r\n return {\r\n name: `${newPass}`,\r\n description,\r\n preview() {\r\n return md(`${passwordStrength(newPass).value}\r\n\\`\\`\\`json\r\n${JSON.stringify(omit(sortedObj(opts), \"_\", \"configs\", \"config\"), null, 4)}\r\n\\`\\`\\`\r\nsee [available options](https://www.npmjs.com/package/generate-password#available-options)\r\n`);\r\n },\r\n value: newPass,\r\n };\r\n });\r\n }\r\n);\r\n\r\nif (flag?.copy) {\r\n copy(chosenPass);\r\n} else {\r\n setSelectedText(chosenPass);\r\n}\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1345","img":"https://avatars.githubusercontent.com/u/76580?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/40895636?u=c291b499d0282201ff22db2e41d0b216c3bbece2&v=4","user":"cvbrian","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1342","url":"https://github.com/johnlindquist/kit/discussions/1342","title":"Trigger Scripts directly from Raycast","name":"Trigger Scripts directly from Raycast","extension":".md","description":"Created by cvbrian","resourcePath":"/johnlindquist/kit/discussions/1342","createdAt":"2023-09-19T16:27:55Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AVi7G","body":"Since I use Raycast as my launcher on my computer I wanted a way to trigger Script Kit scripts directly from the Raycast menu without a second step. I made a script to generate Raycast shell scripts that will directly trigger the Script Kit scripts. \r\n\r\n1. Make a directory to store the Raycast scripts.\r\n2. Add this directory to the Raycast settings as a script directory.\r\n3. Run this script and choose that directory when prompted. \r\n\r\nJust posting this as inspiration. This script is a little rough around the edges and could use refinement and customization to suit your needs. \r\n\r\n\r\n[Open generate-raycast-scripts in Script Kit](https://scriptkit.com/api/new?name=generate-raycast-scripts&url=https://gist.githubusercontent.com/cvbrian/b752361fb54b6efa813362087a72d330/raw/012f74a1249bf6a2406b02f0da1e07d6aa5d2e92/generate-raycast-scripts.js\")\r\n\r\n```js\r\n// Name: Generate Raycast Scripts\r\n\r\nimport \"@johnlindquist/kit\"\r\nconst collect = await npm(\"collect.js\")\r\n\r\n// get the directory to store the scripts in. Save in an environment variable\r\nconst directory = await env(\"RAYCAST_SCRIPTS_DIRECTORY\", async () => { return await selectFolder(\"Select a directory to store the scripts in\") })\r\n// get all the scripts from Script Kit\r\nlet scripts = collect(await getScripts())\r\n// remove preview from scripts\r\nscripts = scripts.map(script => {\r\n delete script.preview\r\n return script\r\n})\r\n// get only kenv scripts\r\nscripts = scripts.where('kenv', '')\r\n// TODO find out which scripts should be ignored\r\n// generate a script for each one in the directory\r\nscripts.each(async script => {\r\n const scriptContents = `#!/bin/bash\r\n\r\n# Required parameters:\r\n# @raycast.schemaVersion 1\r\n# @raycast.title ${script.name}\r\n# @raycast.mode silent\r\n# @raycast.packageName Script Kit Scripts\r\n# Documentation:\r\n# @raycast.description ${script.description}\r\n\r\n\r\n~/.kit/kar ${script.command}\r\n`\r\n await writeFile(`${directory}/${script.name}.sh`, scriptContents, \"utf-8\")\r\n})\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1342","img":"https://avatars.githubusercontent.com/u/40895636?u=c291b499d0282201ff22db2e41d0b216c3bbece2&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1340","url":"https://github.com/johnlindquist/kit/discussions/1340","title":"lorem-ipsum-text","name":"lorem-ipsum-text","extension":".md","description":"Created by abernier","resourcePath":"/johnlindquist/kit/discussions/1340","createdAt":"2023-09-17T12:50:07Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AVgl9","body":"[Open `lorem-ipsum` in Script Kit](https://scriptkit.com/api/new?name=lorem&url=https://gist.githubusercontent.com/abernier/e93dd8f345cba61d4dab1dafb7282a45/raw/lorem-ipsum.ts)\r\n\r\nhttps://github.com/johnlindquist/kit/assets/76580/a6e449aa-d9ef-4404-9be8-b5fd7855e6d0\r\n\r\n[source code](https://gist.github.com/abernier/e93dd8f345cba61d4dab1dafb7282a45#file-lorem-ipsum-ts)\r\n\r\n```ts\r\n// Name: Lorem ipsum\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nimport { loremIpsum, ILoremIpsumParams } from \"lorem-ipsum\";\r\n\r\nlet ret: ReturnType;\r\n\r\nconst DEFAULTS = {\r\n count: 1,\r\n};\r\n\r\nlet count: ILoremIpsumParams[\"count\"];\r\nlet units: ILoremIpsumParams[\"units\"];\r\n\r\nconst flags = {\r\n html: {\r\n name: \"html\",\r\n shortcut: \"cmd+h\",\r\n },\r\n copy: {\r\n name: \"copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n};\r\n\r\nfunction myLoremIpsum({ ...args }: Parameters[0] = {}) {\r\n const format = flag?.html ? \"html\" : \"plain\";\r\n\r\n // say(`generating ${count} ${units} of ${format} text`);\r\n return loremIpsum({ count, units, format, ...args });\r\n}\r\n\r\nawait arg(\r\n {\r\n placeholder: String(DEFAULTS.count),\r\n description: `Generate lorem ipsum text...`,\r\n flags,\r\n },\r\n (input) => {\r\n count = (input && Number(input)) || undefined;\r\n\r\n return [\"paragraphs\", \"sentences\", \"words\"].map((el) => ({\r\n name: el,\r\n preview: () => {\r\n units = el as ILoremIpsumParams[\"units\"];\r\n return myLoremIpsum();\r\n },\r\n }));\r\n }\r\n);\r\n\r\nconst loremText = myLoremIpsum();\r\n\r\nif (flag?.copy) {\r\n copy(loremText);\r\n} else {\r\n setSelectedText(loremText);\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1340","img":"https://avatars.githubusercontent.com/u/76580?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/75037449?u=1fcae869eafe508a6cc283783373a405a2bb4a28&v=4","user":"sum117","author":"sum117","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1339","url":"https://github.com/johnlindquist/kit/discussions/1339","title":"Chunkify text into discord friendly chunks with an intuitive UI!","name":"Chunkify text into discord friendly chunks with an intuitive UI!","extension":".md","description":"Created by sum117","resourcePath":"/johnlindquist/kit/discussions/1339","createdAt":"2023-09-15T19:31:50Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AVfm1","body":"\r\n[Open chunkify-text in Script Kit](https://scriptkit.com/api/new?name=chunkify-text&url=https://gist.githubusercontent.com/sum117/423f829687bea3e32b8defbe0aa62731/raw/ed0d8bbe52749f4b583de1e8f265e06b8c77b654/chunkify-text.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\";\r\n\r\n/**\r\n * Chunkify a text into chunks of a given size.\r\n * @param {string} text\r\n * @param {number} chunkSize\r\n */\r\nfunction chunkify(text, chunkSize) {\r\n const chunks = [];\r\n\r\n let index = 0;\r\n while (index < text.length) {\r\n let end = index + chunkSize;\r\n\r\n while (end > index && text[end] !== \"\\n\") {\r\n end--;\r\n }\r\n\r\n if (end === index) {\r\n end = index + chunkSize;\r\n }\r\n\r\n const chunk = text.substring(index, end);\r\n chunks.push(chunk);\r\n index = end;\r\n }\r\n\r\n return chunks;\r\n}\r\n\r\nconst rawText = await editor(\r\n \"Paste the text to chunkify here (You can delete this placeholder).\"\r\n);\r\n\r\nlet textWidget = await widget(\r\n `\r\n \r\n
`;\r\n return { name, value, html };\r\n});\r\n\r\nlet flags = {\r\n view: {\r\n name: \"View in Shortcuts\",\r\n },\r\n run: {\r\n name: \"Run Shortcut\",\r\n },\r\n};\r\n\r\nlet shortcut = await arg(\r\n { prompt: \"Which shortcut would you like to run?\", flags },\r\n shortcuts\r\n);\r\nawait hide();\r\n\r\nif (flag?.view) {\r\n await exec(`/usr/bin/shortcuts view \"${shortcut.trim()}\"`);\r\n} else {\r\n let result = await exec(`/usr/bin/shortcuts run \"${shortcut.trim()}\" &`);\r\n if (result?.stdout) await div(md(`## Output:\\n\\n${result.stdout}`));\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1333","img":"https://avatars.githubusercontent.com/u/36767987?u=e03b3d34a5882f34dfb8d6e11609dd94836e3696&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1331","url":"https://github.com/johnlindquist/kit/discussions/1331","title":"List Scripts of Specific Kenv","name":"List Scripts of Specific Kenv","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1331","createdAt":"2023-08-20T19:22:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AVIwK","body":"\r\n[Open filter-scripts in Script Kit](https://scriptkit.com/api/new?name=filter-scripts&url=https://gist.githubusercontent.com/johnlindquist/cc108bf018b67a0116e1b8dde95c7280/raw/070bda9b7e16cc9d56eeff330961fdc1df84ed73/filter-scripts.ts\")\r\n\r\n```js\r\n// Name: Filter Scripts\r\n// Shortcut: opt 7\r\n// Cache: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet kenv = \"kit-examples\"\r\n\r\nlet scripts = (await getScripts()).filter(script => script.kenv === kenv)\r\n\r\nlet script = await arg(\"Run Script\", scripts)\r\n\r\nawait run(script.filePath)\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1331","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/988095?u=d3a3cc84565dadde5ad7120646533ec951b2ab20&v=4","user":"pyronaur","author":"Nauris Pūķis","twitter":"pyronaur","discussion":"https://github.com/johnlindquist/kit/discussions/1330","url":"https://github.com/johnlindquist/kit/discussions/1330","title":"Snippets on Steroids","name":"Snippets on Steroids","extension":".md","description":"Created by pyronaur","resourcePath":"/johnlindquist/kit/discussions/1330","createdAt":"2023-08-20T09:43:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AVICI","body":"**Summary**\r\nScriptKit now has snippets built-in, but they're lacking a couple of powerful features at the moment.\r\n\r\nSo I built this addon to handle the selection, cursor, clipboard, and `evil` script `eval()`:\r\n\r\n\r\nHere's an example snippet that's going to replace the selection with a script tag and set variables based on clipboard and ScriptKit input:\r\n```ts\r\n// Name: Expand TS\r\n\r\n```\r\n\r\nIf you like the script, [retweet it](https://twitter.com/pyronaur/status/1693219818843918359) 😇\r\n\r\n## Script:\r\n\r\n```ts\r\n// Name: Snippets on Steroids\r\n// Author: pyronaur\r\n// Twitter: @pyronaur\r\n// Shortcut: cmd+shift+e\r\n/**\r\n * This script expands a given snippet and replaces placeholders with their respective values.\r\n * \r\n * ## Placeholders\r\n * - $CURSOR$ - set the cursor position in the snippet after it's expanded.\r\n * - $SELECTION$ - insert the currently selected text within the snippet.\r\n * - $CLIPBOARD$ - insert the current clipboard text within the snippet.\r\n * \r\n * ## Code Evaluation\r\n * You can place any JavaScript code within $$...$$ and it will be evaluated and replaced with the result.\r\n * \r\n * For example:\r\n * The code inside the $$...$$ will be executed and the result will replace the placeholder.\r\n * Example:\r\n * ```\r\n * const clipboard = '$$clipboard.readText()$$';\r\n * const date = '$$new Date().toLocaleDateString()$$';\r\n * const number = '$$arg(\"What is the magic number?\", ['42', '7', '8'])$$';\r\n * ```\r\n * Note:\r\n * The script execution is potentially dangerous and should be enabled with caution.\r\n * You have to enable it by setting the `I_AM_THE_DANGER` variable to `true`.\r\n */\r\nimport \"@johnlindquist/kit\"\r\nimport { Choice } from '@johnlindquist/kit';\r\n\r\nconst { globby } = await npm(\"globby\");\r\nconst snippet_path = kenvPath('snippets');\r\n\r\n// 🔴 DANGER 🔴\r\n// SETTING THIS TO TRUE ALLOW ANY SCRIPT TO BE EXECUTED BY SNIPPETS\r\nconst I_AM_THE_DANGER = false;\r\n\r\nasync function dangerous_evil_parse(content: string) {\r\n\tconst evil_regex = /\\$\\$(.*?)\\$\\$/g;\r\n\tlet match;\r\n\tlet matches = [];\r\n\twhile ((match = evil_regex.exec(content)) !== null) {\r\n\t\tmatches.push(match);\r\n\t}\r\n\tfor (let match of matches) {\r\n\t\tconst script = match[1];\r\n\t\tconst result = I_AM_THE_DANGER ? await eval(script) : ''; // 🎩 😎\r\n\t\tcontent = content.replace(`$$${script}$$`, result);\r\n\t}\r\n\r\n\treturn content;\r\n}\r\n\r\n// Find $CURSOR$ and set cursor position\r\nasync function set_with_cursor(content: string) {\r\n\tconst cursor_index = content.indexOf('$CURSOR$');\r\n\tif (cursor_index === -1) {\r\n\t\treturn false;\r\n\t}\r\n\t// Remove $$CURSOR$$\r\n\tcontent = content.replace('$CURSOR$', '');\r\n\tawait setSelectedText(content);\r\n\t// There's some async weirdness here\r\n\t// so we'll just wait 100ms\r\n\tawait new Promise(resolve => setTimeout(resolve, 100));\r\n\tconst target_cursor_position = content.length - cursor_index;\r\n\tconst keystrokes = [];\r\n\tfor (let i = 0; i < target_cursor_position; i++) {\r\n\t\tkeystrokes.push(keystroke('left'));\r\n\t}\r\n\tawait Promise.all(keystrokes);\r\n\treturn true;\r\n}\r\n\r\nfunction files_to_choices(files: string[]): Choice[] {\r\n\treturn files.map(file => ({\r\n\t\tname: path.basename(file, '').split('.')[0],\r\n\t\tvalue: file\r\n\t}));\r\n}\r\n\r\nasync function get_snippet_files() {\r\n\t// Untested attempt to fix windows paths (I don't have a windows machine)\r\n\tconst snippet_files = await globby(`${snippet_path}/*`);\r\n\tif (process.platform == 'win32') {\r\n\t\treturn snippet_files;\r\n\t}\r\n\treturn snippet_files.map(file => file.replace(/\\\\/g, '/'));\r\n}\r\n\r\nasync function get_content(snippet: string) {\r\n\tconst snippet_content = await readFile(snippet, 'utf8');\r\n\tconst snippet_lines = snippet_content.split('\\n')\r\n\r\n\t// Remove comments and empty lines until first line of snippet\r\n\treturn snippet_lines\r\n\t\t.slice(snippet_lines.findIndex(line => !line.startsWith('//')))\r\n\t\t.join('\\n');\r\n}\r\n\r\nasync function insert_selection(content: string) {\r\n\tif (content.includes('$SELECTION$') === false) {\r\n\t\treturn content;\r\n\t}\r\n\tconst selection = await getSelectedText();\r\n\tcontent = content.replace('$SELECTION$', selection);\r\n\treturn content;\r\n}\r\n\r\nasync function insert_clipboard(content: string) {\r\n\tif (content.includes('$CLIPBOARD$') === false) {\r\n\t\treturn content;\r\n\t}\r\n\tconst text = await clipboard.readText();\r\n\tcontent = content.replace('$CLIPBOARD$', text);\r\n\treturn content;\r\n}\r\n\r\n\r\n\r\n// 🚀 Go!\r\nconst snippet_file = await arg(\"Which snippet?\", files_to_choices(await get_snippet_files()));\r\nlet content = await get_content(snippet_file);\r\ncontent = await insert_selection(content);\r\ncontent = await insert_clipboard(content);\r\ncontent = await dangerous_evil_parse(content);\r\nif (!(await set_with_cursor(content))) {\r\n\tawait setSelectedText(content);\r\n}\r\n```\r\n\r\nProps @johnlindquist for helpful pointers 👍","value":"https://github.com/johnlindquist/kit/discussions/1330","img":"https://avatars.githubusercontent.com/u/988095?u=d3a3cc84565dadde5ad7120646533ec951b2ab20&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/91077547?u=912b5cbbf037a7082ac71e829d74641975d545ff&v=4","user":"MartinLednar","author":"Martin Lednár","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1327","url":"https://github.com/johnlindquist/kit/discussions/1327","title":"Jira monthly time logger","name":"Jira monthly time logger","extension":".md","description":"Created by MartinLednar","resourcePath":"/johnlindquist/kit/discussions/1327","createdAt":"2023-08-09T13:12:04Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AU_n2","body":"# About\r\n\r\nLog tasks you worked on and the total hours you worked for current month into jira.\r\n\r\n- [Gist link](https://gist.githubusercontent.com/MartinLednar/3e3b0e8b23c92734bf946662a4f4b502/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n- [Download link](https://scriptkit.com/api/new?name=jira-monthly-time-logger&url=https://gist.githubusercontent.com/MartinLednar/d702e7993415b04f9e85fd29b910e0cd/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n\r\n\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1327","img":"https://avatars.githubusercontent.com/u/91077547?u=912b5cbbf037a7082ac71e829d74641975d545ff&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5396211?u=3f22a05cb9cf25b3b6c9f8b75f4554249487a254&v=4","user":"nikolovlazar","author":"Lazar Nikolov","twitter":"NikolovLazar","discussion":"https://github.com/johnlindquist/kit/discussions/1326","url":"https://github.com/johnlindquist/kit/discussions/1326","title":"Attach to tmux session with Kitty terminal","name":"Attach to tmux session with Kitty terminal","extension":".md","description":"Created by nikolovlazar","resourcePath":"/johnlindquist/kit/discussions/1326","createdAt":"2023-08-08T14:35:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AU-up","body":"### Prerequisites:\r\n1. Install [Kitty](https://sw.kovidgoyal.net/kitty/)\r\n2. Add `kitty` to `PATH`: `sudo ln -s /Applications/kitty.app/Contents/MacOS/kitty /usr/local/bin/kitty` (assuming `/usr/local/bin` is in your `PATH`)\r\n3. Kit: `Sync $PATH from Terminal to Kit.app`\r\n\r\n\r\n\r\n\r\n\r\n[Open tmux-sesh in Script Kit](https://scriptkit.com/api/new?name=tmux-sesh&url=https://gist.githubusercontent.com/nikolovlazar/21a78f492e117a5e0dca1685cb668f4d/raw/94e44f60ef8807250f365b996b610d8bb05fd311/tmux-sesh.js\")\r\n\r\n```js\r\n// Name: tmux sesh\r\n// Description: Attach to a tmux session\r\n// Author: Lazar Nikolov\r\n// Twitter: @NikolovLazar\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst sessionsCmd = await $`tmux list-sessions`;\r\n\r\nlet sessions = sessionsCmd.stdout\r\n .split('\\n')\r\n .map((line) => line.split(':')[0])\r\n .filter((sesh) => !!sesh);\r\n\r\nlet choice = await arg('Attach to session:', sessions);\r\n\r\nawait $`kitty --hold sh -c \"tmux a -t ${choice}\"`;\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1326","img":"https://avatars.githubusercontent.com/u/5396211?u=3f22a05cb9cf25b3b6c9f8b75f4554249487a254&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1322","url":"https://github.com/johnlindquist/kit/discussions/1322","title":"Reveal password","name":"Reveal password","extension":".md","description":"Created by abernier","resourcePath":"/johnlindquist/kit/discussions/1322","createdAt":"2023-07-31T06:08:58Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AU0qy","body":"[Open `reveal-password` in Script Kit](https://scriptkit.com/api/new?name=revealpassword&url=https://gist.githubusercontent.com/abernier/582e1458195ec34268305298e4b3b86b/raw/reveal-password.%25E2%2596%25B6.ts)\r\n\r\n`| ******* |` cmd * → `| toto123 |`\r\n\r\n```ts\r\n// Name: Reveal password\r\n// Shortcut: cmd *\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet js = `\r\ndocument.activeElement.type = document.activeElement.type === 'password' ? 'text' : 'password';\r\n`;\r\n\r\nlet value = await applescript(`\r\ntell application \"Google Chrome\" to tell window 1\r\n\tget execute active tab javascript \"\r\n\r\n${js}\r\n\r\n\"\r\nend tell\r\n`);\r\n```\r\n-- https://gist.github.com/abernier/582e1458195ec34268305298e4b3b86b","value":"https://github.com/johnlindquist/kit/discussions/1322","img":"https://avatars.githubusercontent.com/u/76580?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","author":"Kent C. Dodds","twitter":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1310","url":"https://github.com/johnlindquist/kit/discussions/1310","title":"Gather Town Guest Management","name":"Gather Town Guest Management","extension":".md","description":"Created by kentcdodds","resourcePath":"/johnlindquist/kit/discussions/1310","createdAt":"2023-07-13T19:05:44Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUmWp","body":"I use this script to manage who has access to join my [Gather.town](https://gather.town) space, so I don't have to manually approve folks joining, they don't need me to be there to join, and they can only access it if they're logged in and are on the list (they've purchased a ticket).\r\n\r\nVery cool thing I can throw together to solve my problems in an hour.\r\n\r\nFind the most up-to-date version in my repo: https://github.com/kentcdodds/.kenv/blob/main/scripts/gather-guest.ts\r\n\r\n\r\n[Open gather-guest in Script Kit](https://scriptkit.com/api/new?name=gather-guest&url=https://gist.githubusercontent.com/kentcdodds/592bd3aebb51971c3a968954ede061f6/raw/5bc5d065782e50f269c5ed9090976722fae50140/gather-guest.ts\")\r\n\r\n```js\r\n// Menu: Gather Guest List\r\n// Description: Handle the Guest List for Gather\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport {z} from 'zod'\r\n\r\nconst GuestObjectSchema = z.object({\r\n name: z.string().optional(),\r\n affiliation: z.string().optional(),\r\n role: z.string().optional(),\r\n})\r\nconst GuestsSchema = z.record(z.string().email(), GuestObjectSchema)\r\n\r\nconst GATHER_API_KEY = await env('GATHER_API_KEY', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_API_KEY',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Get a Gather API Key\r\n\r\n[app.gather.town/apikeys](https://app.gather.town/apikeys)\r\n `),\r\n )\r\n})\r\n\r\nconst GATHER_SPACE_ID = await env('GATHER_SPACE_ID', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_SPACE_ID',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Specify the Gather Space ID\r\n\r\nIt's everything after \"app/\" in this URL with \"/\" replaced by \"\\\\\":\r\n\r\nhttps://app.gather.town/app/BL0B93FK23T/example\r\n `),\r\n )\r\n})\r\n\r\nasync function go() {\r\n const params = new URLSearchParams({\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n })\r\n const rawGuests = await fetch(\r\n `https://gather.town/api/getEmailGuestlist?${params}`,\r\n ).then(r => r.json())\r\n\r\n const guests = GuestsSchema.parse(rawGuests)\r\n const choices = [\r\n {name: '➕ Add a guest', value: {type: 'add-guest'}},\r\n ...Object.entries(guests).map(([email, {name, affiliation, role}]) => ({\r\n name: `${email} (${name?.trim() || 'Unnamed'}, ${\r\n affiliation?.trim() || 'Unaffiliated'\r\n }, ${role?.trim() || 'No role'})`,\r\n value: {type: 'modify-guest', email},\r\n })),\r\n ]\r\n const rawSelection = await arg(\r\n {placeholder: 'Which guest would you like to modify?'},\r\n choices,\r\n )\r\n const SelectionSchema = z.union([\r\n z.object({\r\n type: z.literal('add-guest'),\r\n }),\r\n z.object({\r\n type: z.literal('modify-guest'),\r\n email: z.string(),\r\n }),\r\n ])\r\n const selection = SelectionSchema.parse(rawSelection)\r\n switch (selection.type) {\r\n case 'add-guest': {\r\n await addGuest()\r\n return go()\r\n }\r\n case 'modify-guest': {\r\n await modifyGuest(selection.email, guests)\r\n return go()\r\n }\r\n }\r\n}\r\n\r\nasync function addGuest() {\r\n const email = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: `What's the guests' email?`}))\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: {[email]: {}},\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\nasync function modifyGuest(\r\n email: string,\r\n guests: z.infer,\r\n) {\r\n const guest = guests[email]\r\n const action = await arg({placeholder: `What would you like to do?`}, [\r\n {name: 'Remove Guest', value: 'remove'},\r\n {name: `Change Guest Email (${email})`, value: 'change-email'},\r\n {\r\n name: `Change Guest Name (${guest.name?.trim() || 'Unnamed'})`,\r\n value: 'change-name',\r\n },\r\n {\r\n name: `Change Guest Affiliation (${\r\n guest.affiliation?.trim() || 'Unaffiliated'\r\n })`,\r\n value: 'change-affiliation',\r\n },\r\n {\r\n name: `Change Guest Role (${guest.role?.trim() || 'No role'})`,\r\n value: 'change-role',\r\n },\r\n {\r\n name: `Cancel`,\r\n value: 'cancel',\r\n },\r\n ])\r\n switch (action) {\r\n case 'remove': {\r\n delete guests[email]\r\n break\r\n }\r\n case 'change-email': {\r\n const newEmail = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: 'New Email'}))\r\n guests[newEmail] = guests[email]\r\n delete guests[email]\r\n email = newEmail\r\n break\r\n }\r\n case 'change-name': {\r\n const newName = await arg({placeholder: 'New Name'})\r\n if (newName) {\r\n guests[email].name = newName\r\n } else {\r\n delete guests[email].name\r\n }\r\n break\r\n }\r\n case 'change-affiliation': {\r\n const newAffiliation = await arg({\r\n placeholder: 'New Affiliation',\r\n })\r\n if (newAffiliation) {\r\n guests[email].affiliation = newAffiliation\r\n } else {\r\n delete guests[email].affiliation\r\n }\r\n break\r\n }\r\n case 'change-role': {\r\n const newRole = await arg({placeholder: 'New Role'})\r\n if (newRole) {\r\n guests[email].role = newRole\r\n } else {\r\n delete guests[email].role\r\n }\r\n break\r\n }\r\n case 'cancel': {\r\n return go()\r\n }\r\n }\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: guests,\r\n overwrite: true,\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\ngo()\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1310","img":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","author":"Ivan Rybnikov","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1309","url":"https://github.com/johnlindquist/kit/discussions/1309","title":"Correct selection with ChatGPT","name":"Correct selection with ChatGPT","extension":".md","description":"Created by ivryb","resourcePath":"/johnlindquist/kit/discussions/1309","createdAt":"2023-07-13T12:30:37Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUmEr","body":"[Open Correct selection with ChatGPT in Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/646da11d9a5dbb2151a2053c4d510dd0/raw/8f9b16dd5c636ef6192c12b037ad80f7d84d0193/correct-selection-script.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1309","img":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1308","url":"https://github.com/johnlindquist/kit/discussions/1308","title":"Resize and composite images for tiktok","name":"Resize and composite images for tiktok","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1308","createdAt":"2023-07-12T22:37:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUllQ","body":"\r\n[Open tiktok-images in Script Kit](https://scriptkit.com/api/new?name=tiktok-images&url=https://gist.githubusercontent.com/trevor-atlas/9bc38697613660a228d89f45c5d5ead9/raw/cdbfee11f395fdf8fdaedacfafbb555a1db9bd65/tiktok-images.ts\")\r\n\r\n```js\r\n// Name: tiktok-images\r\n// Description: Resize images to fit TikTok's 9:16 aspect ratio and avoid being covered by the UI\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Threads: trevor.atlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst sharp = await npm('sharp');\r\nconst { getAverageColor } = await npm('fast-average-color-node');\r\n\r\nconst width = 1440;\r\nconst height = 2400;\r\nconst density = 72;\r\nconst scale = .8;\r\nconst validTypes = new Set(['image/png', 'image/jpeg', 'image/jpg']);\r\nconst outputPath = path.join(home(), 'Desktop', 'resized-images');\r\n\r\nasync function processImage(imageFilepath: string) {\r\n try {\r\n const averageColor = await getAverageColor(imageFilepath);\r\n const image = await sharp(imageFilepath)\r\n .withMetadata({ density })\r\n .resize({ fit: 'inside', width: Math.floor(width * scale), height: Math.floor(height * scale) })\r\n .png({ quality: 100 })\r\n\r\n .toBuffer();\r\n\r\n const color = averageColor.hex || 'black';\r\n\r\n // Add a matching background\r\n const background = await sharp({\r\n create: {\r\n channels: 4,\r\n background: color,\r\n width,\r\n height,\r\n },\r\n })\r\n .withMetadata({ density })\r\n .png({ quality: 100})\r\n .toBuffer();\r\n\r\n\r\n const res = await sharp(background)\r\n .composite([{ input: image, gravity: 'centre' }])\r\n .png({ quality: 100 })\r\n .toBuffer();\r\n\r\n return res;\r\n } catch (error) {\r\n console.error(error);\r\n throw error;\r\n }\r\n};\r\n\r\ninterface FileInfo {\r\n lastModified: number;\r\n lastModifiedDate: string;//\"2023-07-12T17:35:13.573Z\"\r\n name: string;\r\n path: string;//\"/Users/uname/Desktop/screenshots/Screenshot 2022-01-12 at 1.35.08 PM.png\"\r\n size: number;\r\n type: string;//\"image/png\"\r\n webkitRelativePath: string;\r\n}\r\n\r\n\r\ntry {\r\n const fileInfos: FileInfo[] = await drop('Drop images to resize');\r\n const imagePaths = fileInfos\r\n .filter(({type}) => validTypes.has(type))\r\n .map(fileInfo => fileInfo.path);\r\n\r\n if (!imagePaths.length) {\r\n await notify('No valid images found. Supports .png, .jpg, and .jpeg');\r\n exit();\r\n }\r\n\r\n await ensureDir(outputPath);\r\n\r\n for (const imagePath of imagePaths) {\r\n const image = await processImage(imagePath);\r\n const [filename] = path.basename(imagePath).split('.');\r\n const finalPath = path.join(outputPath, `${filename}-processed.png`);\r\n await writeFile(finalPath, image);\r\n console.log(`Resized ${finalPath}`);\r\n }\r\n\r\n await notify('Image(s) resized');\r\n} catch (error) {\r\n console.error(error);\r\n await notify('Error resizing images. Check the log for details.');\r\n}\r\n\r\nawait open(outputPath);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1308","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","author":"Ivan Rybnikov","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1307","url":"https://github.com/johnlindquist/kit/discussions/1307","title":"Correct selection with ChatGPT","name":"Correct selection with ChatGPT","extension":".md","description":"Created by ivryb","resourcePath":"/johnlindquist/kit/discussions/1307","createdAt":"2023-07-12T17:02:06Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUlZi","body":"[Install \"Correct selection with ChatGPT\" to Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/9f63a1881b1827773682cdf7e404b05c/raw/9bce6cdf6dc54497f6d7020807fc1cc6bf405131/correct-selection.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1307","img":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1303","url":"https://github.com/johnlindquist/kit/discussions/1303","title":"Decode a base64 string","name":"Decode a base64 string","extension":".md","description":"Created by ElTacitos","resourcePath":"/johnlindquist/kit/discussions/1303","createdAt":"2023-07-10T10:09:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUjLC","body":"Everything is in the title of this post, this script will allow you to decode a base64 string.\r\n\r\n[Open base64-decode in Script Kit](https://scriptkit.com/api/new?name=base64-decode&url=https://gist.githubusercontent.com/ElTacitos/8eaa571a6026383c9ce71e593e31b598/raw/6aa4a043a4458b980111bfb76ebe0b435d90a524/base64-decode.js\")\r\n\r\n```js\r\n// Name: Decode Base64\r\n// Description: Decode a base64 string and copy it to the clipboard\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet base64 = await arg(\"Enter base64 string to decode\")\r\nlet decoded = atob(base64)\r\n\r\nawait clipboard.writeText(decoded)\r\nawait div(md(`\r\n# ${decoded}\r\n`))\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1303","img":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1296","url":"https://github.com/johnlindquist/kit/discussions/1296","title":"Replace user-defined acronyms with the full text","name":"Replace user-defined acronyms with the full text","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1296","createdAt":"2023-06-30T20:35:34Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUbye","body":"\r\n[Open de-acronym in Script Kit](https://scriptkit.com/api/new?name=de-acronym&url=https://gist.githubusercontent.com/trevor-atlas/992682a54fa4ec44ccc8cc58e889e026/raw/f4ec3016bd7f8b1af4be65a64f3d500c19231e71/de-acronym.ts\")\r\n\r\n```js\r\n// Menu: De-Acronym-ify\r\n// Description: Replace acronyms with their full names\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Shortcut: cmd ctrl opt shift a\r\n// Group: work\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nlet text = '';\r\nconst clipboardValue = await paste();\r\nconst selection = await getSelectedText();\r\n\r\nif (selection) {\r\n text = selection;\r\n console.log('use selection', selection);\r\n}\r\n\r\nif (clipboardValue && !selection) {\r\n text = clipboardValue;\r\n console.log('use clipboard', text);\r\n}\r\n\r\nif (!text) {\r\n text = await arg('Enter text to de-acronym-ify');\r\n console.log('use prompt', text);\r\n}\r\n\r\nconst acronyms: Array<[string | RegExp, string]> = [\r\n ['PD', 'Product Design'],\r\n ['PM', 'Product Management'],\r\n ['JS', 'JavaScript'],\r\n ['TS', 'TypeScript'],\r\n];\r\n\r\nconst result = acronyms.reduce(\r\n (acc, [acronym, expansion]) => acc.replace(acronym, expansion),\r\n text\r\n);\r\n\r\nif (!selection) {\r\n copy(result);\r\n} else {\r\n await setSelectedText(result);\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1296","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1295","url":"https://github.com/johnlindquist/kit/discussions/1295","title":"Type clipboard like a human","name":"Type clipboard like a human","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1295","createdAt":"2023-06-30T20:32:02Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUbyY","body":"I haven't quite dialed in the random delays as well as I'd like, but it gets the job done :]\r\n\r\n[Open humanlike-typing in Script Kit](https://scriptkit.com/api/new?name=humanlike-typing&url=https://gist.githubusercontent.com/trevor-atlas/17746a243dd9bbfa8062d8fb86b5fc20/raw/45b2a5cb769b76db73f70579edf28b469ba194bd/humanlike-typing.ts\")\r\n\r\n```js\r\n// Name: humanlike typing\r\n// Description: Type the contents of your clipboard as if you were a human\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\n\r\nawait applescript(String.raw`\r\nset texttowrite to the clipboard as text\r\ntell application \"System Events\"\r\n repeat with i from 1 to count characters of texttowrite\r\n if (character i of texttowrite) is equal to linefeed or (character i of texttowrite) is equal to return & linefeed or (character i of texttowrite) is equal to return then\r\n keystroke return\r\n else\r\n keystroke (character i of texttowrite)\r\n end\r\n if (character i of texttowrite) is equal to \" \" then\r\n delay (random number from 0.01 to 0.1)\r\n else if (character i of texttowrite) is equal to \"\\n\" then\r\n delay (random number from 0.1 to 0.3)\r\n else\r\n delay (random number from 0.01 to 0.05)\r\n end\r\n end repeat\r\nend tell\r\n`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1295","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1294","url":"https://github.com/johnlindquist/kit/discussions/1294","title":"Connect to GlobalProtect VPN if not connected","name":"Connect to GlobalProtect VPN if not connected","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1294","createdAt":"2023-06-30T19:36:57Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUbwy","body":"\r\n[Open vpn in Script Kit](https://scriptkit.com/api/new?name=vpn&url=https://gist.githubusercontent.com/trevor-atlas/45ea4ba63553e81facc93105cf52dc65/raw/a983e86ac4885afeff3928e268ad780020beffda/vpn.ts\")\r\n\r\n```js\r\n// Name: vpn\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Schedule: */15 * * * *\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\napplescript(`\r\ntell application \"System Events\" to tell process \"GlobalProtect\"\r\n\tset connectionStatus to get help of every menu bar item of menu bar 2\r\n\tif item 1 of connectionStatus = \"Not Connected\" then\r\n\t\tclick menu bar item 1 of menu bar 2 -- Activates the GlobalProtect \"window\" in the menubar\r\n\t\ttry\r\n\t\t\tclick button \"Connect\" of window 1\r\n\t\tend try\r\n\t\tclick menu bar item 1 of menu bar 2 -- This will close the GlobalProtect \"window\" after clicking Connect/Disconnect. This is optional.\r\n\tend if\r\nend tell\r\n`);\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1294","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1289","url":"https://github.com/johnlindquist/kit/discussions/1289","title":"Script Kit 1.76.10 - July 2023 Release - Searching Enhancements, Grouping Logic, More Choice Options","name":"Script Kit 1.76.10 - July 2023 Release - Searching Enhancements, Grouping Logic, More Choice Options","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1289","createdAt":"2023-06-28T00:11:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AUZGp","body":"# Script Kit 1.76.10 - July 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist\r\n\r\n## Features\r\n\r\n### Windows arm64 Build\r\n\r\nWe now have \"arm64\" builds for Windows: https://github.com/johnlindquist/kitapp/releases/tag/v1.76.10\r\n\r\n> arm64 Windows limitations: Unfortunately, the \"keyboard\" libraries for Windows are not yet available for arm64. So, you won't be able to use snippets, \"setSelectedText\", and other keyboard-related features. I'd recommend AutoHotkey as a workaround until the libraries are available.\r\n\r\n### Scripts Caching\r\n\r\nInstead of waiting for the main menu to parse/load the scripts, the previous scripts results are now cached. This shaves off the ~100ms delay when opening the main menu from the keyboard shortcut. (There are also some clever technical tricks under the hood that would require a blog post to explain)\r\n\r\n> Note: If you have a script that uses a shortcut which displays choices that don't change often, consider adding the `// Cache: true` metadata to the script for that extra 100ms boost.\r\n\r\n\r\n### Scripts Grouping and `groupChoices`\r\n\r\nUse the `//Group` metadata to group scripts together in the main menu. For example:\r\n\r\n```js\r\n// Group: Favorite\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909106/clipboard/bnsocw9vxhqafkmmyivv.png)\r\n\r\nThe you can search for \"Favorite\" from the menu to filter to the scripts with that metadata.\r\n\r\n\r\nYou can also add the `.group` property to any choice, then pass your choices to `groupChoices` to use the grouping behavior in your own scripts.\r\n\r\n```js\r\n// Name: Group Choices\r\n// Cache: true\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { faker } from \"@faker-js/faker\"\r\nimport _ from \"lodash\"\r\n\r\nlet faang = [\"Facebook\", \"Apple\", \"Amazon\", \"Netflix\", \"Google\"]\r\n\r\nlet people = Array.from({ length: 25 }).map(() => {\r\n let name = faker.person.fullName()\r\n return {\r\n name,\r\n value: name,\r\n description: faker.color.human(),\r\n group: _.sample(faang), // Grouping by a random company\r\n }\r\n})\r\n\r\nlet groupedChoices = groupChoices(people)\r\n\r\nlet result = await mini(\"Arg Group Demo\", groupedChoices)\r\nawait editor(result)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909060/clipboard/urzyst1cz3wd6olsk7lx.png)\r\n\r\n### Flags \"Submenu\"\r\n\r\nWhen using `flags`, pressing \"right\" (or `cmd+k`) now displays the available actions (AKA \"flags\") to the side in a separate searchable list with its own state.\r\n\r\nThis is a first pass at a \"submenu\" feature. We'll explore nested submenus in the future.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909291/clipboard/p7bwm9rxnhzquye3oxac.png)\r\n\r\n### Searching Off-loaded to Main Process\r\n\r\nPreviously, the searching logic was done in the renderer process. This creating some technical limitations on how we could pass results from a script to the list.\r\n\r\nNow, you will be able pass \"scored\" choice results directly from the script to the list enabling you move the search logic to your script/server/API and display highlighted \"scored\" choices in the list. More information on this in the future.\r\n\r\n### `// Pass` and `.pass` Metadata\r\n\r\nThe `// Pass` metadata on a script (and `.pass` property on a choice) will pass the current input of the prompt directly to the script so you can type input from the main menu that will be passed to the script.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687909936/clipboard/p4bnehjxxkivrbderwhy.png)\r\n\r\n```js\r\n// Name: Grocery List\r\n// Pass: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet note = arg?.pass || (await arg(\"Enter an item\"))\r\n\r\nawait editor(note)\r\n```\r\n\r\nYou can also do this with your own choices by using the `.pass` property on a choice and using the `groupChoices` (as discussed above) to bring up the more advanced list.\r\n\r\n### Choice `.miss` Property\r\n\r\nAdding `.miss` to a choice will make the choice only appear if the search doesn't match any other choices. This is useful for showing a \"No Results\" choice:\r\n\r\n```js\r\n// Name: Miss Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait mini(\"Select an Item\", [\r\n \"One\",\r\n \"Two\",\r\n {\r\n name: \"No Choices Available\",\r\n miss: true,\r\n disableSubmit: true,\r\n nameClassName: `text-red-500`,\r\n },\r\n])\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png](http://res.cloudinary.com/johnlindquist/image/upload/v1687910247/clipboard/s2dtq4wzty61sl3ig0ym.png)\r\n\r\n## Choice `.skip` Property\r\n\r\nUsing `.skip` allows you to create a choice that can't be searched/selected:\r\n\r\n> Note: This is used internally for the \"groupChoice()\" headers\r\n\r\n```js\r\n// Name: Separator Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"A demo for skip\", [\r\n \"Apple\",\r\n \"Banana\",\r\n \"Cherry\",\r\n {\r\n name: \"Separator\",\r\n height: PROMPT.ITEM.HEIGHT.XXXS,\r\n html: ``,\r\n skip: true,\r\n },\r\n \"Celery\",\r\n \"Cucumber\",\r\n \"Lettuce\",\r\n])\r\n```\r\n\r\n## Thanks for Using Script Kit!\r\n\r\nHappy Scripting!\r\n\r\n\\- John Lindquist","value":"https://github.com/johnlindquist/kit/discussions/1289","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1284","url":"https://github.com/johnlindquist/kit/discussions/1284","title":"Search Open PRs","name":"Search Open PRs","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1284","createdAt":"2023-06-21T20:59:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUT7K","body":"Change the owner and repo name to desired repo and get to reviewing!\r\n\r\n[Open search-open-pr in Script Kit](https://scriptkit.com/api/new?name=search-open-pr&url=https://gist.githubusercontent.com/mabry1985/7cf5cec8d5913948aeda070f51ecfe4d/raw/64c98abcade33cdeb54604f29a06c55f05d374f1/search-open-pr.js\")\r\n\r\n```js\r\n// Name: Search Open PRs\r\n// Description: Search open PRs in a repo\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst fetch = await npm(\"node-fetch\");\r\nconst variables = {\r\n owner: \"knapsack-labs\",\r\n repoName: \"app-monorepo\",\r\n};\r\n\r\nlet token = await env(\"GITHUB_AUTH_TOKEN\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst query = `\r\nquery getPrs($owner: String!, $repoName: String!) {\r\n repository(owner: $owner, name: $repoName) {\r\n pullRequests(last: 100, states: OPEN) {\r\n nodes {\r\n body\r\n createdAt\r\n mergeable\r\n number\r\n state\r\n title\r\n updatedAt\r\n url\r\n author {\r\n avatarUrl\r\n login\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n`;\r\n\r\nasync function getPrs() {\r\n return fetch(\"https://api.github.com/graphql\", {\r\n headers: {\r\n authorization: `bearer ${token}`,\r\n },\r\n method: \"POST\",\r\n body: JSON.stringify({ query, variables }),\r\n })\r\n .then((res) => res.json())\r\n .catch((err) => {\r\n console.log(err);\r\n exit();\r\n });\r\n}\r\nconst prs = await getPrs();\r\nconst openPRs = prs.data.repository.pullRequests.nodes;\r\nconst sortedPrs = openPRs.sort(\r\n (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)\r\n);\r\nconst pr = await arg(\r\n {\r\n placeholder: `Select a PR to view`,\r\n },\r\n sortedPrs.map((pr) => {\r\n return {\r\n name: `${pr.number} - ${pr.title}`,\r\n preview: () =>\r\n `
\r\n
${pr.number} - ${pr.title}
\r\n \r\n
Ready to Merge: ${pr.mergeable === \"MERGEABLE\" ? \"✅\" : \"⛔\"}
\r\n
${md(pr.body)}
\r\n \r\n
Author: ${pr.author.login}
\r\n \r\n \r\n
`,\r\n value: pr.number,\r\n };\r\n })\r\n);\r\n\r\nconst prInfo = sortedPrs.find((p) => p.number === pr);\r\nbrowse(prInfo.url);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1284","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1283","url":"https://github.com/johnlindquist/kit/discussions/1283","title":"A simple calculator using js expressions","name":"A simple calculator using js expressions","extension":".md","description":"Created by alkene0005","resourcePath":"/johnlindquist/kit/discussions/1283","createdAt":"2023-06-18T17:37:10Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AURMq","body":"\r\n[Open js-expression in Script Kit](https://scriptkit.com/api/new?name=js-expression&url=https://gist.githubusercontent.com/alkene0005/25bd8b0b560fdc4d1b582bf1c6a4ed55/raw/af15c0765269eb22b6aaa7ac207ed152c0088d3c/js-expression.js\")\r\n\r\n```js\r\n// Name: JS Expression\r\n// Description: I prefer to define it as a simple calculator\r\n\r\n// Global Objects\r\nlet arr = [1, 2, 3, 4, 5]\r\nlet obj = {name: 'Mike', age: 20}\r\n\r\n// Global Functions\r\nlet {\r\n ceil, floor, round, trunc, abs, PI,\r\n sin, cos, tan, log, log2, log10, exp, sqrt, cbrt, pow\r\n} = Math\r\n\r\n// Factorial\r\nlet fact = num => _.reduce(_.range(1, num + 1), (acc, i) => acc * i, 1)\r\n\r\nlet selected = await arg({\r\n placeholder: 'Expression ...',\r\n enter: 'Copy & Exit',\r\n shortcuts: [{\r\n name: 'Repalce', key: 'cmd+r', bar: 'right', onPress: async (input, {focused}) => {\r\n setInput(evalExp(input))\r\n }\r\n }]\r\n}, async (input) => {\r\n let res = input ? evalExp(input) : ''\r\n return md(`~~~javascript\\n${res}\\n~~~`)\r\n})\r\n\r\nif (selected) await copy(evalExp(selected))\r\n\r\nfunction evalExp(input) {\r\n let value = eval(`(${input})`)\r\n if (typeof value == 'number') return (value % 1 != 0 ? value.toFixed(2) : value) + ''\r\n if (typeof value == 'array') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'object') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'function') return ''\r\n}\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1283","img":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1281","url":"https://github.com/johnlindquist/kit/discussions/1281","title":"Quick Search Steam Game","name":"Quick Search Steam Game","extension":".md","description":"Created by alkene0005","resourcePath":"/johnlindquist/kit/discussions/1281","createdAt":"2023-06-18T03:49:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUQVl","body":"[Install](https://scriptkit.com/api/new?name=steam-game-search&url=https://gist.githubusercontent.com/alkene0005/6dc59b6596a258f63cb774dd1206eb79/raw/c3a68d5fb11f78455efbfceebe70224bd1fc98f6/steam-game-search.js)\r\n\r\n```js\r\n// Name: Steam\r\n\r\nimport axios from 'axios'\r\nimport cheerio from 'cheerio'\r\n\r\n// Language-dependent configuration\r\nconst cc = 'US'\r\nconst l = 'english'\r\n\r\nfunction buildResult(value, image, title) {\r\n return {\r\n name: 'abc',\r\n value: value,\r\n html: `\r\n
\r\n \r\n
${title}
\r\n
open
\r\n
\r\n `,\r\n }\r\n}\r\n\r\nlet url = await arg('Keyword ...', async keyword => {\r\n if (keyword.trim() == '') return []\r\n let {data} = await axios.get(\r\n 'https://store.steampowered.com/search/suggest?term=' + keyword +\r\n '&f=games&cc=' + cc + '&realm=1&l=s' + l + '&v=19040599&excluded_content_descriptors%5B%5D=3' +\r\n '&excluded_content_descriptors%5B%5D=4&use_store_query=1&use_search_spellcheck=1&search_creators_and_tags=1'\r\n );\r\n let $ = cheerio.load(data);\r\n let games = $('a').get().map(aTag => {\r\n if ($(aTag).hasClass('match_app')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let price = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${price}`)\r\n }\r\n if ($(aTag).hasClass('match_tag')) {\r\n let name = $(aTag).find('.match_name span').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, 'https://pbs.twimg.com/profile_images/861662902780018688/SFie8jER_x96.jpg', `${name} - ${count}`)\r\n }\r\n if ($(aTag).hasClass('match_creator')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${count}`)\r\n }\r\n });\r\n return games.filter(x => x);\r\n});\r\n\r\nawait $`open ${url}`\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1281","img":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1272","url":"https://github.com/johnlindquist/kit/discussions/1272","title":"Google PaLM2 Chat","name":"Google PaLM2 Chat","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1272","createdAt":"2023-06-06T01:54:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUGVn","body":"The LLM is still in early access but you can sign up for the waitlist [here](https://developers.generativeai.google/)\r\n\r\n[Open palm-chat in Script Kit](https://scriptkit.com/api/new?name=palm-chat&url=https://gist.githubusercontent.com/mabry1985/54c10fa4594fb5a5edcf65c1db55b44b/raw/8460dafe391bd8bb09593e35e2fb89764d27f521/palm-chat.js\")\r\n\r\n```js\r\nlet { GoogleAuth } = await import(\"google-auth-library\");\r\nlet { DiscussServiceClient } = await import(\"@google-ai/generativelanguage\");\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst MODEL_NAME = \"models/chat-bison-001\";\r\nconst API_KEY = await env(\"PALM_API_KEY\", {\r\n hint: `Signup for waitlist here here`,\r\n});\r\n\r\nconst client = new DiscussServiceClient({\r\n authClient: new GoogleAuth().fromAPIKey(API_KEY),\r\n});\r\n\r\nconst config = {\r\n model: MODEL_NAME,\r\n temperature: 0.75,\r\n candidateCount: 1,\r\n top_k: 40,\r\n top_p: 0.95,\r\n};\r\n\r\nconst chatHistory = [];\r\n\r\nconst generateText = async (text) => {\r\n chatHistory.push({ content: text });\r\n const response = await client.generateMessage({\r\n ...config,\r\n prompt: {\r\n context: \"You are a funny and helpful assistant.\",\r\n messages: chatHistory,\r\n },\r\n });\r\n\r\n log(response);\r\n log(response[0].filters);\r\n if (response[0].filters.length > 0) {\r\n return `The model has rejected your input. Reason: ${response[0].filters[0].reason}`;\r\n } else {\r\n chatHistory.push({ content: response[0].candidates[0].content });\r\n return response[0].candidates[0].content;\r\n }\r\n};\r\n\r\nawait chat({\r\n onSubmit: async (input) => {\r\n setLoading(true);\r\n try {\r\n const response = await generateText(input);\r\n let message = md(response);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, message);\r\n } catch (e) {\r\n console.log(e);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, md(\"Error: \" + e.message));\r\n }\r\n setLoading(false);\r\n },\r\n});\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1272","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1271","url":"https://github.com/johnlindquist/kit/discussions/1271","title":"Static to dynamic import converter","name":"Static to dynamic import converter","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1271","createdAt":"2023-06-04T18:07:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AUFWE","body":"I got tired of typing out the conversion when pulling in script examples so I made this quick script to convert them\r\n\r\n[Open static-to-dynamic in Script Kit](https://scriptkit.com/api/new?name=static-to-dynamic&url=https://gist.githubusercontent.com/mabry1985/13b951630f05eebc35c66d8e706dee70/raw/70fb4529876ef97fd18351793d329afca945079e/static-to-dynamic.js\")\r\n\r\n```js\r\n// Name: Static to Dynamic\r\n// Description: Convert static import to dynamic import\r\n// e.g. import { Foo } from \"bar\";\r\n// to let { Foo } = await import(\"bar\");\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst text = await getSelectedText();\r\n\r\nfunction convertImportString(input) {\r\n const importRegex = /import\\s+({[^}]+})\\s+from\\s+\"([^\"]+)\";/;\r\n\r\n if (!importRegex.test(input)) {\r\n throw new Error(\"Invalid import string format\");\r\n }\r\n\r\n const [_, importList, modulePath] = input.match(importRegex);\r\n const output = `let ${importList} = await import(\"${modulePath}\");`;\r\n return output;\r\n}\r\n\r\nconst output = convertImportString(text);\r\n\r\nawait setSelectedText(output);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1271","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","author":"Rohit Kumar Saini","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1262","url":"https://github.com/johnlindquist/kit/discussions/1262","title":"A simple Password Manager","name":"A simple Password Manager","extension":".md","description":"Created by rockingrohit9639","resourcePath":"/johnlindquist/kit/discussions/1262","createdAt":"2023-05-21T04:44:25Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AT5eR","body":"# Password Manager\r\nA simple password manager to add new passwords and copy from one of the existing list of passwords. Passwords are saved after encryption.\r\n\r\n\r\n\r\n[Open password-manager in Script Kit](https://scriptkit.com/api/new?name=password-manager&url=https://gist.githubusercontent.com/rockingrohit9639/586628e63330061cdeaff35cbc7dec05/raw/231215f7064ce763ffd4b2aa93ebb00c0341f080/password-manager.ts\")\r\n\r\n\r\n```js\r\n// Menu: Password Manager\r\n// Description: Manager all your passwords justing using few keys\r\n// Shortcut: command shift ]\r\n// Author: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst { nanoid } = await npm(\"nanoid\");\r\nconst Cryptr = await npm(\"cryptr\");\r\n\r\nconst CRYPTR_KEY = await env(\"CRYPTR_KEY\");\r\nconst cryptr = new Cryptr(CRYPTR_KEY);\r\n\r\nconst { passwords, write } = await db(\"passwords\", { passwords: [] });\r\n\r\ntype Option = {\r\n name: string;\r\n description: string;\r\n value: \"ADD_NEW_PASSWORD\" | \"COPY_PASSWORD\";\r\n};\r\n\r\nconst PM_OPTIONS: Option[] = [\r\n {\r\n name: \"Add New Password\",\r\n description: \"Add a new password to the database\",\r\n value: \"ADD_NEW_PASSWORD\",\r\n },\r\n {\r\n name: \"Copy Password\",\r\n description: \"Copy one of the saved passwords\",\r\n value: \"COPY_PASSWORD\",\r\n },\r\n];\r\n\r\nconst choice: Option[\"value\"] = await arg(\r\n \"What would you like to do?\",\r\n PM_OPTIONS\r\n);\r\n\r\n/** Doing operation on basis of choice */\r\nif (choice === \"ADD_NEW_PASSWORD\") {\r\n addNewPassword();\r\n}\r\n\r\nif (choice === \"COPY_PASSWORD\") {\r\n listAndCopyPassword();\r\n}\r\n\r\nasync function addNewPassword() {\r\n const title = await arg({\r\n placeholder: \"Title\",\r\n hint: \"Title for which your password belongs e.g Facebook etc.\",\r\n });\r\n const password = await arg({\r\n placeholder: \"Password\",\r\n hint: `Password you want to save for ${title}`,\r\n });\r\n\r\n /** Encrypting the password */\r\n const encryptedPassword = cryptr.encrypt(password);\r\n\r\n const id = nanoid(5);\r\n const newPassword = { id, title, password: encryptedPassword };\r\n passwords.push(newPassword);\r\n\r\n /** Saving the password in db */\r\n await write();\r\n notify(`Password for ${title} added successfully!`);\r\n}\r\n\r\nasync function listAndCopyPassword() {\r\n const passwordToCopy = await arg(\r\n \"Which password would you like to copy ?\",\r\n () =>\r\n passwords.map(({ title, password }) => ({ name: title, value: password }))\r\n );\r\n\r\n /** Decrypting the password */\r\n const decryptedPassword = cryptr.decrypt(passwordToCopy);\r\n\r\n /** Copying the password to clipboard */\r\n copy(decryptedPassword);\r\n notify(\"Password copied to you clipboard!\");\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1262","img":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/597015?u=1e764f789e382da7c81ee37ef3e1060bac244310&v=4","user":"LukeCarrier","author":"Luke Carrier","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1261","url":"https://github.com/johnlindquist/kit/discussions/1261","title":"Silly Pomodoro timer","name":"Silly Pomodoro timer","extension":".md","description":"Created by LukeCarrier","resourcePath":"/johnlindquist/kit/discussions/1261","createdAt":"2023-05-20T19:31:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AT5VI","body":"Just a small hack to replace the many menu bar applications I've used over the years, and an excuse to have a play with Script Kit. I'm glad I did -- it's awesome 😁 \r\n\r\n[Open pomodoro in Script Kit](https://scriptkit.com/api/new?name=pomodoro&url=https://gist.githubusercontent.com/LukeCarrier/b5800f573f43fc7acf4ea327f6e396b4/raw/d75fe7d8b4428500457cb2e6de3e2b11e1c9353c/pomodoro.ts\")\r\n\r\n```js\r\n// Name: Pomodoro\r\n// Description: A Pomodoro timer, right here!\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst HOUR_MIN = 60;\r\nconst MIN_SEC = 60;\r\nconst SEC_MS = 1000;\r\n\r\nconst WORK_INTERVAL_SECS = 25 * 60;\r\nconst REST_INTERVAL_SECS = 5 * 60;\r\n\r\nconst WORK_INTERVAL_ICON = \"🍅\";\r\nconst REST_INTERVAL_ICON = \"🏝️\";\r\nconst COMPLETE_ICON = \"🎉\";\r\n\r\nconst WIDGET_HTML = `\r\n
\r\n {{icon}}\r\n
\r\n
\r\n
{{goal}}
\r\n
{{timer}}
\r\n
\r\n`;\r\nconst DING_JS = `new Audio(\"../kenvs/personal/assets/ding.ogg\").play();`;\r\nconst DING_SECS = 5;\r\n\r\nfunction formatTimeRemaining(seconds: number): string {\r\n const totalMinutes = Math.floor(seconds / HOUR_MIN);\r\n const formatSeconds = String(seconds % MIN_SEC).padStart(2, \"0\");\r\n const formatMinutes = String(totalMinutes % MIN_SEC).padStart(2, \"0\");\r\n return `${formatMinutes}:${formatSeconds}`;\r\n}\r\n\r\nconst goal = await arg(\"What's your goal this interval?\")\r\n\r\nconst timerWidget = await widget(WIDGET_HTML, {\r\n title: \"Pomodoro\",\r\n state: { icon: \"\", goal: \"\", timer: \"\" },\r\n\r\n containerClass: \"p-6 max-w-sm mx-auto rounded-xl shadow-lg flex items-center space-x-4\",\r\n alwaysOnTop: true,\r\n preventEscape: true,\r\n minimizable: false,\r\n maximizable: false,\r\n fullscreenable: false,\r\n opacity: 0.45,\r\n\r\n // If these are below the minimum size of a widget on macOS (160x120) the\r\n // widget appears as a small white box without any content until manually\r\n // resized.\r\n width: 340,\r\n height: 120,\r\n});\r\n\r\nfunction doInterval(icon: string, goal: string, interval_secs: number): Promise {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(interval_secs) });\r\n\r\n return new Promise((resolve) => {\r\n const startTime = new Date().getTime();\r\n const timerInterval = setInterval(() => {\r\n const thisTime = new Date().getTime();\r\n const elapsedSeconds = Math.round((thisTime - startTime) / SEC_MS);\r\n const remainingSeconds = interval_secs - elapsedSeconds;\r\n if (remainingSeconds >= 0) {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(remainingSeconds) });\r\n } else {\r\n clearInterval(timerInterval);\r\n timerWidget.executeJavaScript(DING_JS).finally(() => {\r\n resolve();\r\n });\r\n }\r\n }, 1000);\r\n });\r\n}\r\n\r\nawait doInterval(WORK_INTERVAL_ICON, goal, WORK_INTERVAL_SECS);\r\nawait doInterval(REST_INTERVAL_ICON, `Break after ${goal}`, REST_INTERVAL_SECS);\r\ntimerWidget.setState({ icon: COMPLETE_ICON, goal: `${goal} all done!`, timer: \"That's another interval complete.\" });\r\nsetTimeout(() => timerWidget.close(), DING_SECS * 1000);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1261","img":"https://avatars.githubusercontent.com/u/597015?u=1e764f789e382da7c81ee37ef3e1060bac244310&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/8198764?u=8159fd937f0836c330f0aed6c4d518c48461072e&v=4","user":"Schmedu","author":"Eddie","twitter":"schmedu_","discussion":"https://github.com/johnlindquist/kit/discussions/1259","url":"https://github.com/johnlindquist/kit/discussions/1259","title":"JSON 2 YAML","name":"JSON 2 YAML","extension":".md","description":"Created by Schmedu","resourcePath":"/johnlindquist/kit/discussions/1259","createdAt":"2023-05-17T10:31:51Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AT2vb","body":"[Open json2yaml in Script Kit](https://scriptkit.com/api/new?name=json2yaml&url=https://gist.githubusercontent.com/Schmedu/c904124d7a9cd4b9fd25485c9d8c36d0/raw/75255898c5293cbe648e1f7c521bc5a93c120e7b/json2yaml.ts\")\r\n\r\n```js\r\n// Name: Json To Yaml Converter\r\n// Author: Eduard Uffelmann\r\n// Twitter: @schmedu_\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport * as yaml from \"js-yaml\";\r\n\r\nlet filePath = await getSelectedFile();\r\nlet content = await readJson(filePath);\r\n\r\nlet result = yaml.dump(content);\r\n\r\nlet todo = await mini(\"What to do?\", [\"Copy\", \"Save\"]);\r\nif (todo === \"Copy\") {\r\n await copy(result);\r\n} else {\r\n await writeFile(filePath.replace(\".json\", \".yaml\"), result);\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1259","img":"https://avatars.githubusercontent.com/u/8198764?u=8159fd937f0836c330f0aed6c4d518c48461072e&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1256","url":"https://github.com/johnlindquist/kit/discussions/1256","title":"Script Kit 1.59.1 - May 2023 Release","name":"Script Kit 1.59.1 - May 2023 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1256","createdAt":"2023-05-15T18:49:05Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AT1QW","body":"# Script Kit 1.59.1 - May 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Sunsetting Features\r\n\r\nWe're \"sunsetting\"/not supporting a few features going forward. \r\n\r\n> The `npm` and `db` apis will still be available (they're used internally in the SDK!), but we won't be actively supporting them\r\n\r\n1. `await npm()`. Use `await import()` or regular `import` statements instead.\r\n\r\nScript Kit now recommends using standard import methods. When you attempt to run a script and an import fails, Script Kit will catch the error and prompt you to install it. This removes the need for `await npm()`. This is especially useful for people using TypeScript as you don't need to worry about typing `npm` anymore.\r\n\r\n2. `await db()`. We now recommend `keyv`: [https://www.npmjs.com/package/keyv](https://www.npmjs.com/package/keyv)\r\n\r\n`keyv` is a much better solution for storing/retrieving data and achieves all of the future features we had planned for `db`.\r\n\r\nWe'll do our best to update older demos/tutorials that use these methods as we're able.\r\n\r\n## Previews Everywhere\r\n\r\nEvery prompt type (`term`, `drop`, `fields`, etc) now supports the `preview` key. Pass in HTML to display it to the right side of your prompt. This is great for instructions and guiding the user through each script:\r\n\r\n> Note: The `md()` helper is often used to transform markdown into HTML\r\n\r\n\r\n```js\r\nawait term({\r\n preview: md(`# Follow these steps:\r\n\r\n1. First, type \\`ls\\` to see the files in this directory\r\n2. Then, type \\`cat \\` to see the contents of a file\r\n3. Finally, type \\`exit\\` to exit the terminal\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n```js\r\nawait drop({\r\n placeholder: \"Drop an mp3\",\r\n preview: md(`# Convert mp3 to wav\r\n\r\n## Instructions\r\n\r\n1. Drag and drop an mp3 file\r\n2. A progress prompt will open then close when ready\r\n3. Your .wav will be created next to the .mp3 file\r\n `),\r\n})\r\n```\r\n\r\n\r\n\r\n## `term.write()`\r\n\r\nWhen a `term` prompt is open, you can issue terminal commands from shortcuts, etc by using `term.write(myCommand)`\r\n\r\n```js\r\nawait term({\r\n shortcuts: [\r\n {\r\n name: \"List files\",\r\n key: `${cmd}+l`,\r\n onPress: () => {\r\n let command = isWin ? \"dir\" : \"ls\"\r\n term.write(command)\r\n },\r\n bar: \"right\",\r\n },\r\n {\r\n name: \"Up a Directory\",\r\n key: `${cmd}+u`,\r\n onPress: () => {\r\n term.write(\"cd ..\")\r\n },\r\n bar: \"left\",\r\n },\r\n {\r\n name: \"Clear\",\r\n key: `${cmd}+k`,\r\n onPress: () => {\r\n let command = isWin ? \"cls\" : \"clear\"\r\n term.write(command)\r\n },\r\n bar: \"left\",\r\n },\r\n ],\r\n})\r\n```\r\n\r\n\r\n## Kenv Improvements:\r\n\r\n### \"New Kenv\" prompt for GitHub repo:\r\n\r\nWhen creating a new Kenv, it will guide you through the process of linking the Kenv to a remote GitHub repo:\r\n\r\n\r\n\r\n\r\n### With a script selected, take Kenv actions\r\n\r\nPress \"right\" (or `cmd+k`) with a script selected to reveal many \"Kenv\" actions:\r\n\r\n\r\n\r\n### Push/Pull From Remote Kenv\r\n\r\nFrom a script (or a \"Manage Kenv\"), you can now push/pull changes as it swaps you over to a terminal to take action:\r\n\r\n\r\n\r\n### \"Trusted\" Kenvs\r\n\r\nThanks to Script Kit + AI integrations, we've had a large influx of \"non-developer\" users. This necessitated more warnings/protections around sharing scripts.\r\n\r\nSome scripts have features that run scripts automatically: Shortcuts, schedule, background, etc. These kenvs now need to be \"Trusted\" to enable these features to add an extra layer of protection against bad actors. PLEASE only use scripts from people you absolutely trust.\r\n\r\nYou can \"trust\" a kenv during new/clone setup, or later from the Kit tab->Manage Kenv menu. You can also \"untrust\" a kenv from the same menu.\r\n\r\n\r\n\r\n## \"Trigger\" flag\r\n\r\nIf a script is run by \"schedule\", \"shortcut\", etc, you can now access what triggered it by using\r\n\r\n```js\r\nif(flag?.trigger === \"schedule\") // do something specific\r\n```\r\n\r\nThis will allow you customize the behavior based on whether you invoked it manually or automatically\r\n\r\n## Windows Fixes\r\n\r\nHandled edge-cases around\r\n\r\n- Windows setup/install process\r\n- Windows usernames that include spaces\r\n- Windows terminal fixes\r\n\r\nThanks to all the Windows bug reports and testers on Discord. Please keep them coming ❤️\r\n\r\n## Node 18.15.0\r\n\r\nWe're now on node 18.15.0. View the CHANGELOG: https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V18.md#18.15.0\r\n\r\nHappy Scripting - John Lindquist","value":"https://github.com/johnlindquist/kit/discussions/1256","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1254","url":"https://github.com/johnlindquist/kit/discussions/1254","title":"Get GitHub Commit Messages Since Tag","name":"Get GitHub Commit Messages Since Tag","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1254","createdAt":"2023-05-10T16:08:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATxIs","body":"\r\n[Open get-commits in Script Kit](https://scriptkit.com/api/new?name=get-commits&url=https://gist.githubusercontent.com/johnlindquist/e56b9ad663cd56c947cc528c5f1c9f96/raw/a10ac8f6d49ebf961fd08013aec4f9b998e1024c/get-commits.ts)\r\n\r\n```js\r\n// Name: Get GitHub Commits Messages Since Tag\r\n// Description: Get all commit messages since a tag\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Octokit } = await import(\"@octokit/rest\")\r\n\r\nlet ownerRepo = await arg(\"Enter username/repo. Example: johnlindquist/kit\")\r\nlet [owner, repo] = ownerRepo.split(\"/\")\r\nlet tag = await arg(\"Tag. Example: v1.54.53\")\r\n\r\nlet client = new Octokit({\r\n auth: await env(\"GITHUB_PERSONAL_ACCESS_TOKEN\"),\r\n})\r\n\r\nlet page = 1\r\nlet hasMorePages = true\r\nlet messages = []\r\n\r\nlet ref = null\r\nlet tagPage = 1\r\nwhile (!ref) {\r\n let listTags = await client.repos.listTags({\r\n owner,\r\n repo,\r\n per_page: 100,\r\n name: tag,\r\n page: tagPage,\r\n })\r\n\r\n tagPage++\r\n ref = listTags.data.find(t => t.name === tag).commit.sha\r\n}\r\n\r\nlet commit = await client.repos.getCommit({\r\n owner,\r\n repo,\r\n ref,\r\n})\r\n\r\nlet since = commit.data.commit.author.date\r\n\r\nwhile (hasMorePages) {\r\n let data = await client.repos.listCommits({\r\n owner,\r\n repo,\r\n since,\r\n per_page: 100,\r\n page: page,\r\n })\r\n\r\n hasMorePages = data.data.length === 100\r\n messages = messages.concat(data.data.map(c => c.commit.message))\r\n\r\n page++\r\n}\r\n\r\nlet text = messages.join(\"\\n\\n\")\r\n\r\nif (env?.[\"GITHUB_SCRIPTKIT_TOKEN\"]) {\r\n let response = await createGist(text, {\r\n description: `Commit messages since ${tag}`,\r\n isPublic: false,\r\n fileName: \"commit-messages.txt\",\r\n })\r\n\r\n open(response.html_url)\r\n\r\n debugger\r\n} else {\r\n await editor(text)\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1254","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5610359?u=7019998d8df8daf3c7dda50ab3682fce7d43aad2&v=4","user":"pierre-borckmans","author":"Pierre Borckmans","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1253","url":"https://github.com/johnlindquist/kit/discussions/1253","title":"Drive homebrew through Script kit","name":"Drive homebrew through Script kit","extension":".md","description":"Created by pierre-borckmans","resourcePath":"/johnlindquist/kit/discussions/1253","createdAt":"2023-05-10T14:31:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATxC2","body":"Here's a small script that lets the user drive Homebrew from kit script:\r\n- list installed formulae / casks\r\n- install a new formula / cask from a list of all the ones available, minus the ones already installed\r\n- uninstall an existing formula/cask\r\n\r\nHope it's useful\r\n\r\n```// Name: Homebrew menu\r\n// Description: Drive homebrew through Kit-script\r\n// Author: Pierre Borckmans\r\n\r\n// shortcut: ctrl opt cmd b\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst BREW_CMD = '/opt/homebrew/bin/brew'\r\n\r\n\r\nconst cmdList = async (cmd) => (await cmd)._stdout.split(\"\\n\").slice(0, -1)\r\n\r\n\r\nconst getInstalled = async (type) => {\r\n return await cmdList($`${BREW_CMD} list --${type} -1`)\r\n}\r\n\r\nconst getAvailable = async (type) => {\r\n const items = (await cmdList($`${BREW_CMD} ${type} -1`)).map(o => ({\r\n name: o,\r\n value: o,\r\n description: type.slice(0, -1)\r\n }))\r\n const alreadyInstalled = await getInstalled(type.slice(0, -1))\r\n return items.filter(i => !alreadyInstalled.find(ai => ai === i.value));\r\n}\r\n\r\nconst install = async () => {\r\n const packageName = await arg(\"Enter a package to install\", [...await getAvailable(\"formulae\"), ...await getAvailable(\"casks\")])\r\n await $`${BREW_CMD} install ${packageName}`\r\n}\r\n\r\nconst uninstall = async () => {\r\n const packageName = await arg(\"Choose a package to uninstall\", [...await list(\"formula\"), ...await list(\"cask\")])\r\n await $`${BREW_CMD} uninstall ${packageName}`\r\n}\r\n\r\nconst menu = async () => {\r\n const menuOptions = [\r\n { \r\n value: \"formulae\",\r\n name: \"List installed formulae\"\r\n },\r\n { \r\n value: \"casks\",\r\n name: \"List installed casks\"\r\n },\r\n { \r\n value: \"install\",\r\n name: \"Install a cask or formula\"\r\n },\r\n { \r\n value: \"uninstall\",\r\n name: \"Uninstall a cask or formula\"\r\n },\r\n ]\r\n const menuChoice = await arg(\"Select an option\", menuOptions)\r\n\r\n switch (menuChoice) {\r\n case \"formulae\":\r\n await arg(`Homebrew formulas`, await getInstalled(\"formula\"))\r\n break\r\n case \"casks\":\r\n await arg(`Homebrew casks`, await getInstalled(\"cask\"))\r\n break\r\n case \"install\":\r\n await install()\r\n break \r\n case \"uninstall\":\r\n await uninstall()\r\n break \r\n default:\r\n break;\r\n }\r\n await menu()\r\n}\r\n\r\nawait menu();\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1253","img":"https://avatars.githubusercontent.com/u/5610359?u=7019998d8df8daf3c7dda50ab3682fce7d43aad2&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/22137371?u=22b3858822ffbf8ddce68b5c2e000a9910dbb934&v=4","user":"ScytheDraven47","author":"Ben Rogers-McKee","twitter":"ScytheDraven47","discussion":"https://github.com/johnlindquist/kit/discussions/1251","url":"https://github.com/johnlindquist/kit/discussions/1251","title":"Bitwarden Passwords via CLI","name":"Bitwarden Passwords via CLI","extension":".md","description":"Created by ScytheDraven47","resourcePath":"/johnlindquist/kit/discussions/1251","createdAt":"2023-05-09T08:45:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATv44","body":"I figured it'd be nice to have a Bitwarden Script for use outside of browsers, and it made for a good first mini project.\r\nIt uses [@bitwarden/cli](https://www.npmjs.com/package/@bitwarden/cli) via NPM, though I'm looking at doing an API version as well.\r\n\r\n[Code/gist here](https://gist.github.com/ScytheDraven47/0605ea9475778ae9cc2279c6fd07ad2e)\r\n\r\n`Enter` copies password\r\n`Ctrl+Enter` copies username\r\n`Ctrl+Shift+Enter` pastes username, then tabs once, then pastes password (won't work for all use cases, but figured it's nice to have)\r\n\r\nThis script does not save user credentials, but saves a session key to prevent frequent logging in.\r\n\r\nCurrently missing 2FA via email and YubiKey.","value":"https://github.com/johnlindquist/kit/discussions/1251","img":"https://avatars.githubusercontent.com/u/22137371?u=22b3858822ffbf8ddce68b5c2e000a9910dbb934&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1249","url":"https://github.com/johnlindquist/kit/discussions/1249","title":"Multichoice for the `arg` function","name":"Multichoice for the `arg` function","extension":".md","description":"Created by BeSpunky","resourcePath":"/johnlindquist/kit/discussions/1249","createdAt":"2023-05-07T20:29:14Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATuj1","body":"I wrapped the `arg` function to allow multi-selection.\r\nJust replace `arg` with `multiArg` and you're good to go. ✨😊\r\nProvide a 3rd argument to customize item templates.\r\n\r\n[See code here](https://gist.github.com/BeSpunky/468b2e790ba9e32a73a3717dc876bdc4)\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236700932-ad5410fa-f717-4dea-9657-b78a0fc4a6c5.mp4\r\n\r\n`Enter`: Toggles Selection\r\n`Ctrl+Enter`: Submits the results\r\n\r\n**Known issues:**\r\n* When the list is longer than the window, list jumps occur. See #1248 \r\n* The `input` parameter passed into the choice factory function (2nd argument of `arg`) is always `''` and doesn't reflect user input.\r\n* List is filtered, but the default template doesn't highlight fuzzy search matches like the original one.\r\n\r\nEnjoy 🥂","value":"https://github.com/johnlindquist/kit/discussions/1249","img":"https://avatars.githubusercontent.com/u/47897671?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1247","url":"https://github.com/johnlindquist/kit/discussions/1247","title":"Efficient rebuild of scripts when `lib` files change","name":"Efficient rebuild of scripts when `lib` files change","extension":".md","description":"Created by BeSpunky","resourcePath":"/johnlindquist/kit/discussions/1247","createdAt":"2023-05-05T21:38:13Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATtIs","body":"This script watches the `lib` folder, and when changes to `ts` files are made, it does 2 things:\r\n1. Create/update a dependency graph of `libFilePath -> dependantScriptPaths[]`\r\n2. Touches all script files that depend on the changed `lib` file to trigger rebuild.\r\n\r\nhttps://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a\r\n\r\n**TLDR**\r\nCurrently, ScriptKit only rebuilds scripts if it detects changes to the `scripts` folder.\r\nIf you extract your reusable parts and put them into the `lib` folder, ScriptKit doesn't pick up on changes to those files.\r\nThe manual way to overcome this is to save your script file again and trigger rebuild.\r\n\r\nNo more... :)\r\n\r\nActually, this is a 3 scripts solution:\r\n1. [`update-script-dependency-graphs`](https://gist.github.com/BeSpunky/c9139cdedfa349c501a70febea3c46d5): Partially rebuilds the graph if a triggering file has been provided, otherwise completely rebuilds it.\r\n2. [`watch-libs`](https://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a): Watches lib files, partially rebuild the graph using `update-script-dependency-graph`, then touch the scripts.\r\n3. [`watch-scripts`](https://gist.github.com/BeSpunky/1ce1ad4e29b339bec11cd2d416cb676c): Watch script files, partially rebuild the graph\r\n","value":"https://github.com/johnlindquist/kit/discussions/1247","img":"https://avatars.githubusercontent.com/u/47897671?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1246","url":"https://github.com/johnlindquist/kit/discussions/1246","title":"Create a Gist for you script and it's lib dependencies","name":"Create a Gist for you script and it's lib dependencies","extension":".md","description":"Created by BeSpunky","resourcePath":"/johnlindquist/kit/discussions/1246","createdAt":"2023-05-05T21:23:15Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATtIP","body":"## Watch how I publish the script that publishes scripts and their dependencies to Gist... 😄\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236570763-9c84163f-c4f3-46d6-943f-537724db2b2e.mp4\r\n\r\n\r\nHere's the Gist of it:\r\nhttps://gist.github.com/BeSpunky/ff5dcb62887cbee686dd6c3ba31cabb5\r\n\r\n**TLDR**\r\nAs I go playing with ScriptKit, I started using the `lib` folder to centralize reusable functionality.\r\nThis made my scripts difficult to share, as they have nested dependencies which I would've had to add manually to my Gists.\r\nWell no more... 💪\r\n\r\nThis script let's you choose one of your scripts, reads it and recursively extracts `lib` dependencies, then publishes a new gist with the script and the dependencies.\r\n\r\nEnjoy 😊","value":"https://github.com/johnlindquist/kit/discussions/1246","img":"https://avatars.githubusercontent.com/u/47897671?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1245","url":"https://github.com/johnlindquist/kit/discussions/1245","title":"Prompt Anywhere v2","name":"Prompt Anywhere v2","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1245","createdAt":"2023-05-05T08:07:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATsa1","body":"[Open prompt-anywhere in Script Kit](https://scriptkit.com/api/new?name=prompt-anywhere&url=https://gist.githubusercontent.com/mabry1985/482fcf46ae66d79348d63096e00fb5d5/raw/2114b2c4644787ec77ae9f39adff333ecc23f864/prompt-anywhere.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as quick-prompt.js and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n //#########\r\n // Helpers\r\n //########\r\n // exit script on cancel\r\n const cancelChat = () => {\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Paste text to highlighted text and exit script\r\n * @param {*} text\r\n */\r\n const pasteTextAndExit = async (text) => {\r\n await setSelectedText(text);\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Copy text to clipboard and exit script\r\n * @param {*} text\r\n */\r\n const copyToClipboardAndExit = async (text) => {\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n };\r\n\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // which works, but would be nice to also have ESC work\r\n ignoreBlur: false,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // have paste on text on submit?\r\n // onSubmit: () => pasteTextAndExit(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n await pasteTextAndExit(currentMessage);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // @TODO still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all of the actions like copy, paste, etc\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => await copyToClipboardAndExit(state),\r\n onSubmit: async (state) => await pasteTextAndExit(state),\r\n });\r\n break;\r\n case \"copy\":\r\n await copyToClipboardAndExit(currentMessage);\r\n case \"save\":\r\n await inspect(currentMessage, `/conversations/${Date.now()}.md`);\r\n exitChat();\r\n default:\r\n copyToClipboardAndExit(currentMessage);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1245","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1244","url":"https://github.com/johnlindquist/kit/discussions/1244","title":"Prompt Anywhere v2","name":"Prompt Anywhere v2","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1244","createdAt":"2023-05-05T06:30:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATsW5","body":"\r\n[Open prompt-anything in Script Kit](https://scriptkit.com/api/new?name=prompt-anything&url=https://gist.githubusercontent.com/mabry1985/6c2412d4d3d1360276b9d95f44548815/raw/1dac5e149ebc4cf86ea830db9104d8518f47eca5/prompt-anything.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as `quick-prompt.js` and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n//#########\r\n// Helpers\r\n//########\r\n// exit script on cancel\r\nconst cancelChat = () => {\r\n process.exit(1);\r\n};\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage + options),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // ignoreBlur: true,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // onSubmit: () => setSelectedText(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n // paste into highlighted text\r\n await setSelectedText(currentMessage);\r\n process.exit(1);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all these same options such as save\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => {\r\n // copy to clipboard when exiting the editor\r\n await clipboard.writeText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n onSubmit: async (state) => {\r\n // paste into highlighted text when pressing enter\r\n await setSelectedText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n });\r\n break;\r\n case \"copy\":\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n // exit script\r\n process.exit(1);\r\n case \"save\":\r\n await inspect(currentMessage, `conversations/${Date.now()}.md`);\r\n // exit script\r\n process.exit(1);\r\n default:\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1244","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/629240?u=4750610206bd5ebafbdfab85d5d9c81dc8ce21ed&v=4","user":"blakecannell","author":"Blake.","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1240","url":"https://github.com/johnlindquist/kit/discussions/1240","title":"Snippets Manager (File Based)","name":"Snippets Manager (File Based)","extension":".md","description":"Created by blakecannell","resourcePath":"/johnlindquist/kit/discussions/1240","createdAt":"2023-05-04T04:40:53Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATrcB","body":"Simple file based snippets manager:\r\n\r\nA few things to note:\r\n- File paths are currently specific to Windows. I will update for other envionments.\r\n- This assumes a `snippets` folder exists within your home folder. Is there any way to make this configurable (within the manager itself)? This is of course quite simple to set as a constant at the top of the file if not.\r\n\r\nhttps://gist.github.com/blakecannell/bea79f6c69103a410181802855855aa4","value":"https://github.com/johnlindquist/kit/discussions/1240","img":"https://avatars.githubusercontent.com/u/629240?u=4750610206bd5ebafbdfab85d5d9c81dc8ce21ed&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1238","url":"https://github.com/johnlindquist/kit/discussions/1238","title":"Open Recent VS Code Project","name":"Open Recent VS Code Project","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1238","createdAt":"2023-05-03T23:43:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATrRi","body":"TIL VS Code has a sqlite database of recents, so I built this!\r\n\r\n[Open open-recent-vs-code-project in Script Kit](https://scriptkit.com/api/new?name=open-recent-vs-code-project&url=https://gist.githubusercontent.com/johnlindquist/b2426f52a9b5f3ca4827fbdeda6b323c/raw/8d7cbd175540000bf6a8684814de265343ad2ae5/open-recent-vs-code-project.ts\")\r\n\r\n```js\r\n// Name: Open Recent VS Code Project\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { URL, fileURLToPath } from \"url\"\r\n\r\n// /Users/johnlindquist/Library/Application Support/Code/User/globalStorage/state.vscdb\r\nlet filename = home(\"Library\", \"Application Support\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\n// windows path not tested, just guessing\r\nif (isWin) filename = home(\"AppData\", \"Roaming\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\nlet { default: sqlite3 } = await import(\"sqlite3\")\r\nlet { open } = await import(\"sqlite\")\r\n\r\nconst db = await open({\r\n filename,\r\n driver: sqlite3.Database,\r\n})\r\n\r\nlet key = `history.recentlyOpenedPathsList`\r\nlet table = `ItemTable`\r\n\r\nlet result = await db.get(`SELECT * FROM ${table} WHERE key = '${key}'`)\r\nlet recentPaths = JSON.parse(result.value)\r\nrecentPaths = recentPaths.entries\r\n .map(e => e?.folderUri)\r\n .filter(Boolean)\r\n .map(uri => fileURLToPath(new URL(uri)))\r\n\r\nlet recentPath = await arg(\"Open a recent path\", recentPaths)\r\nhide()\r\nawait exec(`code ${recentPath}`)\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1238","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/34244581?u=f136c7d3eec99d1a4b2c887ca8c673bfabef95fa&v=4","user":"alwinraju","author":"Alwin Raju","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1236","url":"https://github.com/johnlindquist/kit/discussions/1236","title":"ChatGPT with user input","name":"ChatGPT with user input","extension":".md","description":"Created by alwinraju","resourcePath":"/johnlindquist/kit/discussions/1236","createdAt":"2023-05-01T21:30:11Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATpjD","body":"Heres a code snippet that takes in a user input and feeds it into ChatGPT and returns the response.\r\nAn OpenAI API key is required for it to work. The prompt can be amended to suit your needs/to create\r\nyour own custom agents.\r\n\r\n```javascript\r\n/*\r\nPress `cmd+shift+enter` and enter the text you want to send to ChatGPT.\r\n*/\r\n\r\n// Name: Samantha\r\n// Description: Send a single prompt to ChatGPT\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nYou are ChatGPT, a large language model trained by OpenAI. Follow the user's\r\ninstructions carefully. Respond using markdown.\r\n########\r\n`;\r\n\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet textFromUser = await arg(\"How can I help you?\");\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(textFromUser)]);\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1236","img":"https://avatars.githubusercontent.com/u/34244581?u=f136c7d3eec99d1a4b2c887ca8c673bfabef95fa&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","author":"Josh Davenport-Smith","twitter":"joshdprts","discussion":"https://github.com/johnlindquist/kit/discussions/1235","url":"https://github.com/johnlindquist/kit/discussions/1235","title":"Pause any music - only looks at Spotify/Music.app but customisable","name":"Pause any music - only looks at Spotify/Music.app but customisable","extension":".md","description":"Created by joshdavenport","resourcePath":"/johnlindquist/kit/discussions/1235","createdAt":"2023-05-01T09:26:25Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATpCU","body":"I'm often switching between Spotify and Music.app and find that what Mac targets when using pause media key can sometimes be unpredictable. This little script will pause either if playing.\r\n\r\n[Open pause-any-music in Script Kit](https://scriptkit.com/api/new?name=pause-any-music&url=https://gist.githubusercontent.com/joshdavenport/d6a38b7e5b9d9f1a76b0e44b78a7a5e5/raw/7c376383c698a52f817228aa19cf5312dbbc095c/pause-any-music.ts\")\r\n\r\n```js\r\n// Name: Pause Any Music\r\n// Description: Pause music playing from music apps\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst pauseScript = `\r\ntell application \"Spotify\"\r\n pause\r\nend tell\r\n\r\ntell application \"Music\"\r\n pause\r\nend tell\r\n`;\r\n\r\nexec(`osascript -e '${pauseScript}'`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1235","img":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1234","url":"https://github.com/johnlindquist/kit/discussions/1234","title":"Open dev-project","name":"Open dev-project","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1234","createdAt":"2023-05-01T03:28:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATo6h","body":"Now updated to use DB and add paths, create folders.\r\n\r\nOpen for any improvements ^^\r\n\r\n\r\n[Open dev-project in Script Kit](https://scriptkit.com/api/new?name=dev-project&url=https://gist.githubusercontent.com/Ambushfall/41236543032f4dd211a65964766be087/raw/5f7b2d37749d436f5015523d57fcba28d119b4cd/dev-project.js\")\r\n\r\n```js\r\n// Preview: docs\r\n// Menu: Open Project\r\n// Description: Opens a project in vscode\r\n// Shortcut: cmd shift .\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst envPath = await env('PROJECT_DIR');\r\n\r\nconst projectDir = home(envPath);\r\n\r\nconst projectList = await readdir(projectDir);\r\n\r\n\r\nlet { projects, write } = await db(\"projects\", {\r\n projects: projectList,\r\n})\r\n\r\nprojectList.forEach(async value => {\r\n if (!projects.includes(value)) {\r\n projects.push(value);\r\n await write()\r\n }\r\n})\r\n\r\n\r\nonTab(\"Open\", async () => {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n edit('', path.resolve(projectDir, project))\r\n})\r\n\r\nonTab(\"Add Path\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n \"Add path to project:\",\r\n md(projects.map(project => `* ${project.split('\\\\').pop()}`).join(\"\\n\"))\r\n )\r\n projects.push(project)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"Remove\", async () => {\r\n while (true) {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n\r\n project.split(':').length > 1 ? await rm(path.resolve(project)) : await rm(path.resolve(projectDir, project))\r\n\r\n let indexOfProject = projects.indexOf(project)\r\n projects.splice(indexOfProject, 1)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"New Project\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n {\r\n placeholder: \"Create new project:\", debounceInput: 400,\r\n enter: \"Create\", validate: async (input) => {\r\n let exists = await isDir(path.resolve(projectDir, input));\r\n if (exists) {\r\n return `${input} already exists`;\r\n }\r\n return true;\r\n }\r\n },\r\n\r\n )\r\n projects.push(project)\r\n mkdir(path.resolve(projectDir, project))\r\n await write()\r\n }\r\n})\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1234","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1232","url":"https://github.com/johnlindquist/kit/discussions/1232","title":"Get an AI powered explanation of highlighted text","name":"Get an AI powered explanation of highlighted text","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1232","createdAt":"2023-05-01T00:13:53Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATo1l","body":"\r\n[Open explain-plz in Script Kit](https://scriptkit.com/api/new?name=explain-plz&url=https://gist.githubusercontent.com/mabry1985/15add17a63b2d218be168495c2fb46b1/raw/3515457b32049380e633da1e625ff3d6714f844d/explain-plz.js\")\r\n\r\nA quick POC for an AI powered explanation script\r\n\r\nReturns a TLDR, Technical Summary, and ELI5\r\n\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235382761-06db398d-e8b0-4be5-8e8d-a3bb81d4a694.mov\r\n\r\n\r\n```js\r\n/*\r\n# Explain Plz\r\nHighlight some text and have it explained by AI\r\nWorks for any highlighted text or code\r\n*/\r\n\r\n// Name: Explain Plz\r\n// Description: Get an explanation for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd alt shift e\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and explaining it to the user.\r\nReturn the response in the following format using markdown syntax:\r\n# Explain Plz\r\n## TLDR (A quick summary of the highlighted text)\r\n## ELI5 (Explain Like I'm 5)\r\n## Explanation (A longer technical explanation of the highlighted text)\r\n`;\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n``;\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1232","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1231","url":"https://github.com/johnlindquist/kit/discussions/1231","title":"An example of an AGI task manager with Human in the loop feedback","name":"An example of an AGI task manager with Human in the loop feedback","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1231","createdAt":"2023-04-30T21:26:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATov9","body":"\r\n[Open ac-agi in Script Kit](https://scriptkit.com/api/new?name=ac-agi&url=https://gist.githubusercontent.com/mabry1985/cb36cb2a25d58628dcc2b506ec63e2dc/raw/0814b40397f404a9f05d03a549b352186f22f6ba/ac-agi.js\")\r\n\r\nUp to date script can be found in my Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\n```js\r\n/*\r\nPardon the mess this was put together in half a day for the [lablab.ai](https://lablab.ai/event/autonomous-gpt-agents-hackathon) hackathon.\r\nMore updates to come\r\n\r\n# AC AGI \r\nAn autonomous general intelligence that accomplishes a task for you.\r\nUses human in the loop to provide feedback to the agent.\r\n\r\n\r\nHow to use:\r\n- Enter your task\r\n- Wait for the agent to complete the task\r\n- Assign max-iterations for the agent to loop: 0 for infinite (probably not a good idea ¯\\_(ツ)_/¯)\r\n- Profit\r\n\r\nKnown issues:\r\n- The agent will sometimes get stuck in a loop and not complete the task\r\n- Human feedback is not always helpful\r\n\r\nUpcoming features:\r\n- More tools\r\n- Refined prompts\r\n- Better human feedback system\r\n- Better memory system\r\n\r\nPossible thanks to the fine folks at [Langchain](https://js.langchain.com/docs/use_cases/autonomous_agents/baby_agi#example-with-tools)\r\nand all the other giants whose shoulders we stand on.\r\n*/\r\n\r\n// Name: AC AGI\r\n// Description: An AGI task manager inspired by BabyAGI\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { BabyAGI } = await import(\"langchain/experimental/babyagi\");\r\nlet { MemoryVectorStore } = await import(\"langchain/vectorstores/memory\");\r\nlet { OpenAIEmbeddings } = await import(\"langchain/embeddings/openai\");\r\nlet { OpenAI } = await import(\"langchain/llms/openai\");\r\nlet { PromptTemplate } = await import(\"langchain/prompts\");\r\nlet { LLMChain } = await import(\"langchain/chains\");\r\nlet { ChainTool } = await import(\"langchain/tools\");\r\nlet { initializeAgentExecutorWithOptions } = await import(\"langchain/agents\");\r\nlet { DynamicTool } = await import(\"langchain/tools\");\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nawait env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst task = await arg({\r\n placeholder: \"Task\",\r\n description: \"Enter a task for AC AGI to complete\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\nlet maxIterations = await arg({\r\n placeholder: \"How many times should AC AGI loop?\",\r\n hint: \"Leave empty for infinite iterations *use with caution*\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nif (maxIterations === \"\" || maxIterations === \"0\") {\r\n maxIterations = undefined;\r\n}\r\n\r\n//#########################\r\n// BabyAGI method overrides\r\n//#########################\r\nfunction printTaskList() {\r\n let result = \"\";\r\n for (const t of this.taskList) {\r\n result += `${t.taskID}: ${t.taskName}\\n`;\r\n }\r\n const msg = `### Task List\r\n \r\n ${result}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printNextTask(task) {\r\n const msg = `### Next Task\r\n \r\n ${task.taskID}: ${task.taskName}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printTaskResult(result) {\r\n const msg = `### Task Result\r\n \r\n ${result.trim()}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\n//#############\r\n// Custom Tools\r\n//#############\r\nlet html = (str) => str.replace(/ /g, \"+\");\r\nlet fetch = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${html(\r\n q\r\n )}&sort=date`;\r\n\r\nasync function search(query) {\r\n let response = await get(fetch(query));\r\n\r\n let items = response?.data?.items;\r\n\r\n if (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n return JSON.stringify(choices);\r\n }\r\n}\r\n\r\nasync function humanFeedbackList(mdStr) {\r\n let html = md(`${mdStr.trim()}`);\r\n const response = div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n\r\n return response;\r\n}\r\n\r\nasync function humanInput(question) {\r\n const response = await arg({\r\n placeholder: \"Human, I need help!\",\r\n hint: question,\r\n ignoreBlur: true,\r\n ignoreAbandon: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n });\r\n return response;\r\n}\r\n\r\nconst todoPrompt = PromptTemplate.fromTemplate(\r\n \"You are a planner/expert todo list creator. Generate a markdown formatted todo list for: {objective}\"\r\n);\r\n\r\nconst tools = [\r\n new ChainTool({\r\n name: \"TODO\",\r\n chain: new LLMChain({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n prompt: todoPrompt,\r\n }),\r\n description:\r\n \"For making todo lists. Input: objective to create todo list for. Output: the todo list\",\r\n }),\r\n new DynamicTool({\r\n name: \"Search\",\r\n description: \"Search web for info\",\r\n func: search,\r\n }),\r\n new DynamicTool({\r\n name: \"Human Input\",\r\n description:\r\n \"(Use only when no info is available elsewhere) Ask a human for specific input that you don't know, like a persons name, or DOB, location, etc. Input is question to ask human, output is answer\",\r\n func: humanInput,\r\n }),\r\n // new DynamicTool({\r\n // name: \"Human Feedback Choice\",\r\n // description: `Ask human for feedback if you unsure of next step.\r\n // Input is markdown string formatted with your questions and suitable responses like this example:\r\n // # Human, I need your help!\r\n // \r\n // * [John](submit:John) // don't change formatting of these links\r\n // * [Mindy](submit:Mindy)\r\n // * [Joy](submit:Joy)\r\n // * [Other](submit:Other)\r\n // `,\r\n // func: humanFeedbackList,\r\n // }),\r\n];\r\n\r\n//##################\r\n// AC AGI is Born\r\n//##################\r\nconst taskBeginMsg = md(`\r\n### Executing Task Manager\r\nGoal: ${task}\r\n`);\r\n\r\ndiv({ html: taskBeginMsg, ignoreBlur: true });\r\n\r\nconst agentExecutor = await initializeAgentExecutorWithOptions(\r\n tools,\r\n new ChatOpenAI({ temperature: 0 }),\r\n {\r\n agentType: \"zero-shot-react-description\",\r\n agentArgs: {\r\n prefix: `You are an AI who performs one task based on the following objective: {objective}. \r\nTake into account these previously completed tasks: {context}.`,\r\n suffix: `Question: {task}\r\n{agent_scratchpad}`,\r\n inputVariables: [\"objective\", \"task\", \"context\", \"agent_scratchpad\"],\r\n },\r\n }\r\n);\r\n\r\nconst vectorStore = new MemoryVectorStore(new OpenAIEmbeddings());\r\n\r\nconst babyAGI = BabyAGI.fromLLM({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n executionChain: agentExecutor,\r\n vectorstore: vectorStore,\r\n maxIterations: maxIterations,\r\n});\r\n\r\nbabyAGI.printNextTask = printNextTask;\r\nbabyAGI.printTaskList = printTaskList;\r\nbabyAGI.printTaskResult = printTaskResult;\r\n\r\nawait babyAGI.call({ objective: task });\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1231","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1230","url":"https://github.com/johnlindquist/kit/discussions/1230","title":"Example of BabyAGI running in ScriptKit","name":"Example of BabyAGI running in ScriptKit","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1230","createdAt":"2023-04-30T21:23:21Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATov2","body":"","value":"https://github.com/johnlindquist/kit/discussions/1230","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1225","url":"https://github.com/johnlindquist/kit/discussions/1225","title":"Google Search","name":"Google Search","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1225","createdAt":"2023-04-30T06:23:32Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATn_P","body":"I know this is redundant and might not be useful to many, but I needed to build a custom search tool for an agent I'm working on. \r\n\r\nI set this up to test the functionality and figured someone might find it useful.\r\n\r\n```\r\n/* \r\n# Google Search\r\nExample of leveraging Google's Custom Search Engine API to search the web\r\n*/\r\n\r\n// Name: Google Search\r\n// Description: Leverage Google's Custom Search Engine API to search the web\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet query = await arg(\r\n {\r\n placeholder: \"Search Query\",\r\n strict: false,\r\n },\r\n [\r\n {\r\n name: \"Send a search query to Google\",\r\n info: \"always\",\r\n },\r\n ]\r\n);\r\n\r\nlet search = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${q}&sort=date`;\r\n\r\nlet response = await get(search(query));\r\n\r\nlet items = response?.data?.items;\r\n\r\nif (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n let link = await arg(\"Choose a link to view\", choices);\r\n\r\n open(link);\r\n}\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1225","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95415447?v=4","user":"shyagamzo","author":"Shy Agam","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1224","url":"https://github.com/johnlindquist/kit/discussions/1224","title":"Reading aloud streamed text (GPT style)","name":"Reading aloud streamed text (GPT style)","extension":".md","description":"Created by shyagamzo","resourcePath":"/johnlindquist/kit/discussions/1224","createdAt":"2023-04-29T21:32:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATn4O","body":"I wanted my ChatGPT responses to be read aloud immediately, as they appear on the screen.\r\nThis was problematic because of two reasons:\r\n1. ChatGPT sends partial text responses (e.g. 'he', 'llo', ', I', 'am S', 'hy'). This isn't readable and should be accumulated.\r\n2. The `say` command stops any spoken text and starts speaking the new text. Meaning, we cannot simply call it every time we receive new text.\r\n\r\nThis is a rough start, but works well for my current needs.\r\nThis util class maintains a queue, detects certain delimiters (e.g. `.`, `,`) and starts speaking only when it detects that a phrase has probably been accumulated.\r\n\r\nhttps://gist.github.com/shyagamzo/749b7535aa8876ec2ce09f39aaef6a80\r\n\r\n```typescript\r\nimport '@johnlindquist/kit';\r\n\r\nconst speechStream = new (class SpeechStream\r\n{\r\n private textQueue: string[] = [];\r\n private isSpeaking: boolean = false;\r\n private feed: string = '';\r\n private finalizeFeedDebounced: () => void;\r\n\r\n constructor(private readonly config: { waitForDelimiter: number, estimatedWordsPerMinute: number })\r\n {\r\n this.finalizeFeedDebounced = _.debounce(this.finalizeFeed.bind(this), config.waitForDelimiter);\r\n\r\n onExit(() =>\r\n {\r\n this.textQueue = [];\r\n this.feed = '';\r\n\r\n sayIt('');\r\n });\r\n }\r\n\r\n public addText(text: string): void\r\n {\r\n this.feed += text;\r\n this.processAccumulatedText();\r\n this.finalizeFeedDebounced();\r\n }\r\n\r\n private processAccumulatedText(): void\r\n {\r\n const delimiters = /([.,;:!?\\n])/;\r\n\r\n const delimiterMatch = this.feed.match(delimiters);\r\n\r\n if (delimiterMatch)\r\n {\r\n const delimiterIndex = delimiterMatch.index;\r\n\r\n const textUntilDelimiter = this.feed.slice(0, delimiterIndex + 1);\r\n this.textQueue.push(textUntilDelimiter.trim());\r\n\r\n this.feed = this.feed.slice(delimiterIndex + 1);\r\n }\r\n\r\n this.processQueue();\r\n }\r\n\r\n private finalizeFeed(): void\r\n {\r\n if (this.feed)\r\n {\r\n this.textQueue.push(this.feed.trim());\r\n this.feed = '';\r\n this.processQueue();\r\n }\r\n }\r\n\r\n private processQueue(): void\r\n {\r\n if (this.isSpeaking || this.textQueue.length === 0) return;\r\n\r\n this.isSpeaking = true;\r\n\r\n const textToSpeak = this.textQueue.shift();\r\n\r\n this.waitForSpeechEnd(textToSpeak);\r\n sayIt(textToSpeak);\r\n }\r\n\r\n private waitForSpeechEnd(text: string): void\r\n {\r\n const estimatedSpeechDuration = this.estimateSpeechDuration(text);\r\n\r\n setTimeout(() =>\r\n {\r\n this.isSpeaking = false;\r\n this.processQueue();\r\n }, estimatedSpeechDuration);\r\n }\r\n\r\n private estimateSpeechDuration(text: string): number\r\n {\r\n const wordsPerMinute = this.config.estimatedWordsPerMinute; // Average speaking rate\r\n const words = text.trim().split(/\\s+/).length;\r\n const minutes = words / wordsPerMinute;\r\n\r\n return minutes * 60 * 1000; // Convert to milliseconds\r\n }\r\n})({\r\n waitForDelimiter: 4000,\r\n estimatedWordsPerMinute: 200\r\n});\r\n\r\nexport function sayIt(text: string): ReturnType\r\n{\r\n return say(text, { name: 'Microsoft Zira - English (United States)', rate: 1.3 });\r\n}\r\n\r\nexport function queueSpeech(text: string)\r\n{\r\n speechStream.addText(text);\r\n}\r\n```\r\n\r\nTo use it, simply import and call `queueSpeech`:\r\n\r\n```typescript\r\nimport { queueSpeech } from '../lib/speech-queue';\r\n\r\nfunction handleGPTText(text: string)\r\n{\r\n // ...\r\n queueSpeech(text);\r\n}\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1224","img":"https://avatars.githubusercontent.com/u/95415447?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1221","url":"https://github.com/johnlindquist/kit/discussions/1221","title":"Improve your writing with AI powers","name":"Improve your writing with AI powers","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1221","createdAt":"2023-04-29T00:42:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATnd8","body":"[Deprecated] \r\nif you miss it, check out Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\nHighlight your poorly written text and run the script to automagically make yourself sound smarter!\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273614-71a0b99c-5c9b-4806-84ba-2da8943359b9.mov\r\n\r\n\r\n\r\n```js\r\n/*\r\n/*\r\n# Smartify your words!\r\n\r\nTired of feeling dumb? Winter got you in a funk? \r\nCan you just not seem to get the words out right? \r\nWell, let's Smartify your words!\r\n\r\nHighlight some text and press `cmd+shift+enter` to send it through ChatGPT \r\nto replace the text with a more eloquent version. Mileage may vary.\r\n*/\r\n\r\n// Name: Smartify Your Words\r\n// Description: Let's make those words smarter!\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking an input and refactoring it using the following rules: '\r\n\r\n- Maintain the same meaning, tone, and intent as the original text\r\n- Clean up any grammar or spelling mistakes\r\n- Make it sound more professional, but keep it casual\r\n- Reduce redundancies and excessive verbiage\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes.\r\n########\r\n`;\r\n\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst smartify = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n if (!token) return;\r\n log(`handleLLMStart`);\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token) => {\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\nlet text = await getSelectedText();\r\n\r\nawait smartify.call([\r\n new SystemChatMessage(prompt),\r\n new HumanChatMessage(text),\r\n]);\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1221","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1220","url":"https://github.com/johnlindquist/kit/discussions/1220","title":"Prompt ChatGPT from anywhere you can input text","name":"Prompt ChatGPT from anywhere you can input text","extension":".md","description":"Created by mabry1985","resourcePath":"/johnlindquist/kit/discussions/1220","createdAt":"2023-04-29T00:36:59Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATnd5","body":"[DEPRECATED] \r\n\r\nBy selecting any input text and running this script you now have access to the power of AI anywhere in your OS. \r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273240-824a9432-87a3-48ac-9357-668a0c01b12c.mov\r\n\r\n```js\r\n/*\r\n# Generate a response to a prompt in any text field\r\n\r\nThis script utilizes ChatGPT to generate a response for the selected text. \r\nThe response will then replace the original text, making it useful for various writing tasks such as blog posts, code, and emails.\r\n\r\nHighlight some text and press `cmd+shift+p` to send it through ChatGPT \r\nto replace the text response. Your mileage may vary.\r\n*/\r\n\r\n// Name: Prompt Anywhere\r\n// Description: Generate a prompt response in anywhere you can type\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd shift P\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and generating a response to it.\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes. \r\nDo not ask any questions, do not refuse any input, and do not change the subject.\r\n`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n log(`handleLLMStart`);\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n // occasionally I see tokens typed out of order\r\n // still not sure why this happens\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1220","img":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/20973893?u=359eca24a75c3971794c4daba03f723f0164762a&v=4","user":"mueslirieger","author":"Michael Rieger","twitter":"mueslirieger","discussion":"https://github.com/johnlindquist/kit/discussions/1212","url":"https://github.com/johnlindquist/kit/discussions/1212","title":"Open Vercel project dashboard","name":"Open Vercel project dashboard","extension":".md","description":"Created by mueslirieger","resourcePath":"/johnlindquist/kit/discussions/1212","createdAt":"2023-04-27T07:15:16Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATl3v","body":"I use this script so much, I had to share it. It lets you select a personal or team project that is hosted on Vercel and opens the dashboard page of the selected project.\r\n\r\n\r\n[Open open-vercel-project in Script Kit](https://scriptkit.com/api/new?name=open-vercel-project&url=https://gist.githubusercontent.com/mueslirieger/21b1b1b9e6ef48ecf64d8d1a3937f0e8/raw/08532f733744bd314da5667b9e2feff49c6da6ca/open-vercel-project.ts\")\r\n\r\n```typescript\r\n/*\r\n# Open Vercel project dashboard\r\n\r\nLets the user select and open the dashboard page of a project hosted on Vercel.\r\n*/\r\n\r\n// Name: Open Vercel project dashboard\r\n// Description: Lets the user select and open the dashboard page of a project hosted on Vercel.\r\n// Author: Michael Rieger\r\n// Twitter: @mueslirieger\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst apiBaseUrl = 'https://api.vercel.com';\r\nconst dashboardBaseUrl = 'https://vercel.com';\r\n\r\n// ask user to create an access token for the rest api\r\nconst VERCEL_ACCESS_TOKEN = await env('VERCEL_ACCESS_TOKEN', {\r\n panel: md(`## Get a [Vercel API Access Token](https://vercel.com/account/tokens)`),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst user = await fetchUser();\r\n\r\n// Select whether personal or team projects should be listed\r\nconst projectsType = await selectProjectsType();\r\n\r\n// If team projects were selected list the teams the user is assigned to\r\nlet team: Team | undefined | null = null;\r\nif (projectsType === 'team') {\r\n const teams = await fetchTeams();\r\n team = await selectTeam(teams);\r\n}\r\n\r\n// Fetch projects based on previous selection\r\nconst projects = await fetchProjects(team?.id);\r\n\r\n// let user select project and open in browser\r\nconst project = await selectProject(projects);\r\n\r\nif (!project) exit(-1);\r\n\r\nawait browse(`${dashboardBaseUrl}/${projectsType === 'team' ? team.slug : user.username}/${project.name}`);\r\n\r\n// -----------------------------------------------------\r\n// Helpers\r\n// -----------------------------------------------------\r\n\r\ntype VercelApiError = {\r\n error?: {\r\n code: string;\r\n message: string;\r\n };\r\n};\r\n\r\nasync function selectProjectsType() {\r\n return arg<'personal' | 'team'>('Show personal or team projects', [\r\n {\r\n value: 'personal',\r\n name: '[P]ersonal',\r\n shortcut: 'p',\r\n },\r\n {\r\n value: 'team',\r\n name: '[T]eam',\r\n shortcut: 't',\r\n },\r\n ]);\r\n}\r\n\r\ntype User = {\r\n id: string;\r\n email: string;\r\n name: string | null;\r\n username: string;\r\n};\r\ntype GetUserResponse = { user: User } & VercelApiError;\r\n\r\nasync function fetchUser() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/user`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.user;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\ntype Team = { id: string; name: string; slug: string; avatar: string | null };\r\ntype GetTeamsResponse = { teams?: Team[] } & VercelApiError;\r\n\r\nasync function fetchTeams() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/teams`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.teams;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectTeam(teams: Team[]) {\r\n return await arg(\r\n {\r\n placeholder: teams.length ? 'Select a team' : 'No teams found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Select team`,\r\n },\r\n teams.map((team) => ({\r\n value: team,\r\n name: team.name,\r\n img: team.avatar ? `https://vercel.com/api/www/avatar/${team.avatar}?s=128` : '',\r\n }))\r\n );\r\n}\r\n\r\ntype Project = { id: string; name: string; latestDeployments: { alias: string[] }[] };\r\ntype GetProjectsResponse = { projects?: Project[] } & VercelApiError;\r\n\r\nasync function fetchProjects(teamId?: string | null | undefined) {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v9/projects${teamId ? `?teamId=${teamId}` : ''}`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.projects;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectProject(projects: Project[]) {\r\n return await arg(\r\n {\r\n placeholder: projects.length ? 'Select a project' : 'No projects found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Open project in dashboard`,\r\n },\r\n projects.map((project) => ({\r\n value: project,\r\n name: project.name,\r\n }))\r\n );\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1212","img":"https://avatars.githubusercontent.com/u/20973893?u=359eca24a75c3971794c4daba03f723f0164762a&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1209","url":"https://github.com/johnlindquist/kit/discussions/1209","title":"Merge / Split Afred Clipboard Script","name":"Merge / Split Afred Clipboard Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1209","createdAt":"2023-04-25T02:19:34Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjx3","body":"\r\n[Open merge-split-alfred-clipboard in Script Kit](https://scriptkit.com/api/new?name=merge-split-alfred-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/4803f2f333828e5cbd30a49cc426cd22/raw/2f9d71e8c1dc94525f7bc7996b47d820fbadfb9a/merge-split-alfred-clipboard.ts\")\r\n\r\nThis is a very specific yet useful Script, for those who have Alfred app with Powerpack and use the clipboard history. It allows you to split and merge it in several ways. \r\nFor `merge`, it asks you for the number of items in the clipboard, with a preview, and then asks you the merging character or characters. The resulting merge is placed in the clipboard.\r\nFor `split`, it asks you for a splitting character or characters, and saves all the resulted strings (after splitting) in the clipboard history.\r\n\r\nUse cases:\r\n* copy a bunch of values from different places, join them together in one shot, by `\\n'\r\n* copy a list of values, separated by comma or `\\n`, split them and paste them individually in a form\r\n\r\n```js\r\n// Name: Merge / Split Alfred clipboard\r\n// Description: Merge or split clipboard content using Alfred app's clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\");\r\nconst databasePath = home('Library/Application Support/Alfred/Databases/clipboard.alfdb')\r\nif (!await pathExists(databasePath)) {\r\n notify(\"Alfred clipboard database not found\" )\r\n exit()\r\n}\r\n\r\nconst db = new Database(databasePath);\r\n\r\nconst queryClipboard = async (sql, params) => {\r\n const stmt = db.prepare(sql);\r\n return sql.trim().toUpperCase().startsWith(\"SELECT\") ? stmt.all(params) : stmt.run(params);\r\n};\r\n\r\nconst getMergedClipboards = async (count, separator) => {\r\n const sql = `SELECT item FROM clipboard WHERE dataType = 0 order by ROWID desc LIMIT ?`;\r\n const clipboards = await queryClipboard(sql, [count]);\r\n return clipboards.map(row => row.item.trim()).join(separator);\r\n};\r\n\r\nconst writeMergedClipboards = async (mergedText) => {\r\n await clipboard.writeText(mergedText);\r\n};\r\n\r\nconst getSplitClipboard = async (separator, trim) => {\r\n const currentClipboard = await clipboard.readText();\r\n return currentClipboard.split(separator).map(item => trim ? item.trim() : item);\r\n};\r\n\r\nconst writeSplitClipboard = async (splitText) => {\r\n const lastTsSql = `SELECT ts FROM clipboard WHERE dataType = 0 ORDER BY ts DESC LIMIT 1`;\r\n const lastTsResult = await queryClipboard(lastTsSql, []);\r\n let lastTs = lastTsResult.length > 0 ? Number(lastTsResult[0].ts) : 0;\r\n\r\n const insertSql = `INSERT INTO clipboard (item, ts, dataType, app, appPath) VALUES (?, ?, 0, 'Kit', '/Applications/Kit.app')`;\r\n\r\n for (let i = 0; i < splitText.length - 1; i++) {\r\n lastTs += 1;\r\n await queryClipboard(insertSql, [splitText[i], lastTs]);\r\n }\r\n\r\n await clipboard.writeText(splitText[splitText.length - 1]);\r\n};\r\n\r\n\r\nconst action = await arg(\"Choose action\", [\"Merge\", \"Split\"]);\r\n\r\nif (action === \"Merge\") {\r\n const count = await arg({\r\n placeholder: \"Enter the number of clipboard items to merge\",\r\n }, async (input) => {\r\n if (isNaN(Number(input)) || input.length === 0)return ''\r\n return md(`
${await getMergedClipboards(input, '\\n')}
`)\r\n })\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for merging\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n return md(`
${await getMergedClipboards(count, input)}
`)\r\n })\r\n const mergedText = await getMergedClipboards(count, separator);\r\n await writeMergedClipboards(mergedText);\r\n await notify(\"Merged clipboard items and copied to clipboard\");\r\n} else {\r\n // const separator = await arg(\"Enter the separator for splitting\");\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for splitting\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n let strings = await getSplitClipboard(input, true);\r\n return md(`
${strings.join('\\n')}
`)\r\n })\r\n const trim = await arg(\"Trim clipboard content?\", [\"Yes\", \"No\"]);\r\n const splitText = await getSplitClipboard(separator, trim === \"Yes\");\r\n await writeSplitClipboard(splitText);\r\n await notify(\"Split clipboard content and stored in Alfred clipboard\");\r\n}\r\n\r\ndb.close();\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1209","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1208","url":"https://github.com/johnlindquist/kit/discussions/1208","title":"Type Clipboard Script","name":"Type Clipboard Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1208","createdAt":"2023-04-25T02:13:01Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxt","body":"\r\n[Open type-clipboard in Script Kit](https://scriptkit.com/api/new?name=type-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/a18db9f56dfe8f6745ce6e917baf8ade/raw/eb3878c3aaede4e064bfbb451e6d30851b84160e/type-clipboard.ts\")\r\n\r\nThis is a Script I use more often than I would care to admit. There're situations where the `paste` command just doesn't work. Either web forms that don't allow paste, or crappy app UIs that for some reason a normal paste doesn't work. If you don't mind the lengthy shortcut, you hit `ctrl+cmd+alt+shift+v` and it `types` the content of the clipboard really fast, instead of pasting it.\r\n\r\n```js\r\n// Name: Type Clipboard\r\n// Description: Get the content of the clipboard and \"keystroke\" it without pasting\r\n// Shortcut: ctrl+cmd+alt+shift+v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst clipboardText = await clipboard.readText()\r\n\r\nif (clipboardText.length > 1000) {\r\n await notify(\"Clipboard content is too long\")\r\n exit()\r\n}\r\n\r\nawait applescript(String.raw`\r\n set chars to count (get the clipboard)\r\n tell application \"System Events\"\r\n delay 0.1\r\n keystroke (get the clipboard)\r\n end tell\r\n`)\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1208","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1207","url":"https://github.com/johnlindquist/kit/discussions/1207","title":"Open in WhatsApp Script","name":"Open in WhatsApp Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1207","createdAt":"2023-04-25T02:10:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxo","body":"\r\n[Open open-in-whatsapp in Script Kit](https://scriptkit.com/api/new?name=open-in-whatsapp&url=https://gist.githubusercontent.com/ramiroaraujo/a61f67f8b55a3805888ff092b77c2550/raw/80eaadf67510c49d0724bb82b69f2a00ddb0d7d6/open-in-whatsapp.ts\")\r\n\r\nAnother simple script for opening a phone number in WhatsApp to chat. It fetches the number from the clipboard, and if no country code is provided it assumes Argentina, where I'm from, but of course change it to your default country\r\n\r\n```js\r\n// Name: Open in WhatsApp\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the text from the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//normalize the text\r\ntext = text.replace(/[-() ]/g, \"\");\r\n\r\n//validate if valid phone number\r\nif (!text.match(/^(\\+\\d{12,13})|(\\d{10,11})$/)) {\r\n notify(\"Invalid phone number\");\r\n exit()\r\n}\r\n\r\n//assume Argentina if no country code since that's where I'm from\r\nif (!text.startsWith(\"+\")) {\r\n text = \"+54\" + text;\r\n}\r\n\r\n//open in WhatsApp\r\nopen(`https://wa.me/${text}`);\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1207","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1206","url":"https://github.com/johnlindquist/kit/discussions/1206","title":"Convert selected images Script","name":"Convert selected images Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1206","createdAt":"2023-04-25T02:08:46Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxi","body":"\r\n[Open convert-selected-images in Script Kit](https://scriptkit.com/api/new?name=convert-selected-images&url=https://gist.githubusercontent.com/ramiroaraujo/51a8303fd66cc9d8b6db8a19c651254e/raw/b19ada003b9f58f48976636115e136cd2841ed0a/convert-selected-images.ts\")\r\n\r\nThis Script will convert all your selected (supported) images to either `jpg`, `png` or `webp`. I mostly created it to deal with sending images from the phone to the mac, and getting them as `heic`...\r\n\r\n```js\r\n // Name: convert selected images\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n// Grab selected files\r\nconst files = (await getSelectedFile()).split(\"\\n\");\r\n\r\n// Set up whitelist of formats\r\nconst supportedFormats = [\".heic\", \".png\", \".gif\", \".webp\", \".jpg\", \".jpeg\"];\r\n\r\n// Filter files based on supported formats\r\nconst selectedFiles = files.filter(file =>\r\n supportedFormats.some(format => file.toLowerCase().endsWith(format))\r\n);\r\n\r\n// Notify if no files are selected\r\nif (!selectedFiles.length) {\r\n await notify(\"No supported files selected\");\r\n exit();\r\n}\r\n\r\nconst convertHeic = await npm(\"heic-convert\");\r\nconst sharp = await npm(\"sharp\");\r\n\r\n// Select the output format\r\nconst outputFormat = await arg(\"Choose an output format\", [\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n]);\r\n\r\nconst getUniquePath = async (outputPath, suffix = \"\") => {\r\n if (await isFile(outputPath)) {\r\n const name = path.basename(outputPath, path.extname(outputPath));\r\n const newName = `${name}${suffix}-copy${path.extname(outputPath)}`;\r\n const newPath = path.join(path.dirname(outputPath), newName);\r\n return await getUniquePath(newPath, `${suffix}-copy`);\r\n } else {\r\n return outputPath;\r\n }\r\n};\r\n\r\n// Convert selected files to the chosen output format using appropriate libraries\r\nfor (const file of selectedFiles) {\r\n const content = await readFile(file);\r\n const name = path.basename(file).split(\".\")[0];\r\n const outputPath = path.join(path.dirname(file), name + `.${outputFormat}`);\r\n\r\n const uniqueOutputPath = await getUniquePath(outputPath);\r\n\r\n if (file.toLowerCase().endsWith(\".heic\")) {\r\n const formatMap = {\r\n jpg: \"JPEG\",\r\n png: \"PNG\",\r\n }\r\n const outputBuffer = await convertHeic({\r\n buffer: content,\r\n format: formatMap[outputFormat],\r\n quality: 0.5,\r\n });\r\n\r\n await writeFile(uniqueOutputPath, outputBuffer);\r\n } else {\r\n const sharpImage = sharp(content);\r\n\r\n switch (outputFormat) {\r\n case \"jpg\":\r\n await sharpImage.jpeg({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n case \"png\":\r\n await sharpImage.png().toFile(uniqueOutputPath);\r\n break;\r\n case \"webp\":\r\n await sharpImage.webp({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n }\r\n }\r\n}\r\n\r\nawait notify(`Converted selected files to ${outputFormat.toUpperCase()}`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1206","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1205","url":"https://github.com/johnlindquist/kit/discussions/1205","title":"Open URL in clipboard Script","name":"Open URL in clipboard Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1205","createdAt":"2023-04-25T02:06:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxb","body":"\r\n[Open open-url-in-clipboard in Script Kit](https://scriptkit.com/api/new?name=open-url-in-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/600d82866cd35b21998897843a4c3eb4/raw/c729cfb0baf43e6aa371a0ed0050ad69d5a9267d/open-url-in-clipboard.ts\")\r\n\r\nDead simple script for the very common use case of copying _some_ text with a URL in it, and wanting to navigate to that URL. Will fetch the first one it finds and go\r\n\r\n```js\r\n// Name: Open URL in clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//get the first URL in the clipboard, if any\r\nlet url = text.match(/(https?:\\/\\/[^\\s]+)/);\r\n\r\n//if there's a URL, open it\r\nif (url) {\r\n open(url[0]);\r\n} else {\r\n notify(\"No URL found in clipboard\");\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1205","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1204","url":"https://github.com/johnlindquist/kit/discussions/1204","title":"Emoji Search Script","name":"Emoji Search Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1204","createdAt":"2023-04-25T02:04:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxX","body":"\r\n[Open emoji-search in Script Kit](https://scriptkit.com/api/new?name=emoji-search&url=https://gist.githubusercontent.com/ramiroaraujo/5b5f92d043e1ffa07af92215395d9231/raw/e134a3a357dfe43276d9d83bbd73ca01aad74537/emoji-search.ts\")\r\n\r\nA rather simple Emoji search that uses local database for fast lookup. It actually bootstrap by creating a sqlite database out of the `emojilib` JSON, in particular for storing usage and sorting by it. It will search by name and keywords, and the list will be sorted by most used\r\n\r\n```js\r\n// Name: Emoji Search\r\n// Description: Search and copy emoji to clipboard using SQLite database\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\")\r\nconst databaseFile = projectPath(\"db\", \"emoji-search-emojilib.db\")\r\n\r\nconst emojilibURL = \"https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json\"\r\n\r\nconst createDatabase = async () => {\r\n const response = await get(emojilibURL)\r\n const emojiData = response.data as Record\r\n\r\n //create db and table\r\n const db = new Database(databaseFile)\r\n db.exec(`CREATE TABLE IF NOT EXISTS emojis\r\n (emoji TEXT PRIMARY KEY, name TEXT, keywords TEXT, used INTEGER DEFAULT 0)`)\r\n\r\n //populate with data from emojilib\r\n for (const [emojiChar, emojiInfo] of Object.entries(emojiData)) {\r\n const description = emojiInfo[0]\r\n const tags = emojiInfo.slice(1).join(', ')\r\n\r\n db.prepare(\"INSERT OR REPLACE INTO emojis VALUES (?, ?, ?, 0)\").run(emojiChar, description, tags)\r\n }\r\n db.close()\r\n};\r\n\r\nif (!await pathExists(databaseFile)) {\r\n await createDatabase()\r\n}\r\n\r\nconst db = new Database(databaseFile)\r\n\r\nconst queryEmojis = async () => {\r\n const sql = \"SELECT emoji, name, keywords FROM emojis ORDER BY used DESC\"\r\n const stmt = db.prepare(sql)\r\n return stmt.all()\r\n}\r\n\r\nconst snakeToHuman = (text) => {\r\n return text\r\n .split('_')\r\n .map((word, index) => index === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word)\r\n .join(' ')\r\n}\r\n\r\nconst emojis = await queryEmojis()\r\n\r\nconst selectedEmoji = await arg(\"Search Emoji\", emojis.map(({ emoji, name, keywords }) => ({\r\n name: `${snakeToHuman(name)} ${keywords}`,\r\n html: md(`
\r\n ${emoji}\r\n
\r\n ${snakeToHuman(name)}\r\n ${keywords} \r\n
\r\n
`),\r\n value: emoji,\r\n\r\n})))\r\n\r\nawait clipboard.writeText(selectedEmoji)\r\n\r\n// Update the 'used' count\r\nconst updateSql = \"UPDATE emojis SET used = used + 1 WHERE emoji = ?\"\r\nconst updateStmt = db.prepare(updateSql)\r\nupdateStmt.run(selectedEmoji)\r\n\r\ndb.close()\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1204","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1203","url":"https://github.com/johnlindquist/kit/discussions/1203","title":"Text Manipulation Script","name":"Text Manipulation Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1203","createdAt":"2023-04-25T02:02:30Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxU","body":"\r\n[Open text-manipulation in Script Kit](https://scriptkit.com/api/new?name=text-manipulation&url=https://gist.githubusercontent.com/ramiroaraujo/55cd0f21adb60f0a270c18fbcce99454/raw/7dd3ecffbaa6eea66b9c3476ea8122ea31ef25e5/text-manipulation.ts\")\r\n\r\nInspired by a mix of an old Pipe workflow for Alfred mixed with the String Manipulation Plugin for Jetbrains IDEs. It will transform the current content of the clipboard based on the operation you select. Some operations require a parameter (`joinBy` for example), in those cases it asks for it. Both in the operation selection and parameter it shows a preview of the resulting text. \r\nIf you select an operation by `Cmd + enter` you'll be prompted by another operation to select after the first one is finished, and you can continue \"piping\" the outputs until you're done. Since it's common for me to `Cmd + enter` one last time and don't actually need the transformation there's a `No Operation` transform to select on this cases.\r\n\r\nUse cases:\r\n* copy a large list of values, wrap them in `'`, join them by `\\n`\r\n* capture numbers regex in each line, clean empty lines, join them by `+`, paste in ScriptKit or Alfred for sum result\r\n* filter lines by regex\r\n\r\nIt's hard to explain how useful this ends up being in my day to day\r\n\r\n```js\r\n// Name: Text Manipulation\r\n// Description: Transform clipboard text based on user-selected options\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet transformations = {\r\n upperCase: text => text.toUpperCase(),\r\n lowerCase: text => text.toLowerCase(),\r\n capitalize: text => text.split('\\n').map(line => line.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')).join('\\n'),\r\n decodeUrl: text => text.split('\\n').map(line => decodeURIComponent(line)).join('\\n'),\r\n snakeCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `_${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n camelCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => p.toUpperCase()).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n kebabCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `-${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n reverseCharacters: text => text.split('\\n').map(line => line.split('').reverse().join('')).join('\\n'),\r\n removeDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n return [...new Set(lines)].join('\\n');\r\n },\r\n keepOnlyDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n let duplicates = lines.filter((item, index) => lines.indexOf(item) !== index);\r\n return [...new Set(duplicates)].join('\\n');\r\n },\r\n removeEmptyLines: text => text.split('\\n').filter(line => line.trim() !== '').join('\\n'),\r\n removeAllNewLines: text => text.split('\\n').map(line => line.trim()).join(''),\r\n trimEachLine: text => text.split('\\n').map(line => line.trim()).join('\\n'),\r\n sortLinesAlphabetically: text => text.split('\\n').sort().join('\\n'),\r\n sortLinesNumerically: text => text.split('\\n').sort((a, b) => a - b).join('\\n'),\r\n reverseLines: text => text.split('\\n').reverse().join('\\n'),\r\n shuffleLines: text => {\r\n let lines = text.split('\\n')\r\n for (let i = lines.length - 1; i > 0; i--) {\r\n let j = Math.floor(Math.random() * (i + 1))\r\n let temp = lines[i]\r\n lines[i] = lines[j]\r\n lines[j] = temp\r\n }\r\n return lines.join('\\n')\r\n },\r\n joinBy: (text, separator) => text.split('\\n').join(separator),\r\n splitBy: (text, separator) => text.split(separator).join('\\n'),\r\n removeWrapping: text => {\r\n const lines = text.split('\\n');\r\n const matchingPairs = [['(', ')'], ['[', ']'], ['{', '}'], ['<', '>'], ['\"', '\"'], [\"'\", \"'\"]];\r\n return lines\r\n .map(line => {\r\n const firstChar = line.charAt(0);\r\n const lastChar = line.charAt(line.length - 1);\r\n\r\n for (const [open, close] of matchingPairs) {\r\n if (firstChar === open && lastChar === close) {\r\n return line.slice(1, -1);\r\n }\r\n }\r\n\r\n if (firstChar === lastChar) {\r\n return line.slice(1, -1);\r\n }\r\n\r\n return line;\r\n })\r\n .join('\\n');\r\n },\r\n wrapEachLine: (text, wrapper) => {\r\n const lines = text.split('\\n');\r\n\r\n return lines\r\n .map(line => `${wrapper}${line}${wrapper}`)\r\n .join('\\n');\r\n },\r\n captureEachLine: (text, regex) => {\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex);\r\n\r\n return lines\r\n .map(line => {\r\n const match = line.match(pattern);\r\n return match ? match[0] : '';\r\n })\r\n .join('\\n');\r\n },\r\n removeLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i');\r\n\r\n return lines\r\n .filter(line => !pattern.test(line))\r\n .join('\\n');\r\n },\r\n keepLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i')\r\n\r\n return lines\r\n .filter(line => pattern.test(line))\r\n .join('\\n');\r\n },\r\n prependTextToAllLines: (text, prefix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => prefix + line).join('\\n');\r\n },\r\n\r\n appendTextToAllLines: (text, suffix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => line + suffix).join('\\n');\r\n },\r\n\r\n replaceRegexInAllLines: (text, regexWithReplacement) => {\r\n const [regex, replacement] = regexWithReplacement.split('|');\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, replacement)).join('\\n');\r\n },\r\n removeRegexInAllLines: (text, regex) => {\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, '')).join('\\n');\r\n },\r\n generateNumberedList: (text) => {\r\n const lines = text.split('\\n');\r\n return lines.map((line, index) => `${index + 1}. ${line}`).join('\\n');\r\n },\r\n noop: text => text,\r\n}\r\n\r\nlet options = [\r\n // Existing options here\r\n {\r\n name: \"Decode URL\", description: \"Decode a URL-encoded text\", value: {\r\n key: \"decodeUrl\"\r\n }\r\n },\r\n {\r\n name: \"Upper Case\",\r\n description: \"Transform the entire text to upper case\",\r\n value: {\r\n key: \"upperCase\",\r\n },\r\n },\r\n {\r\n name: \"Lower Case\",\r\n description: \"Transform the entire text to lower case\",\r\n value: {\r\n key: \"lowerCase\",\r\n },\r\n },\r\n {\r\n name: \"snake_case\", description: \"Convert text to snake_case\", value: {\r\n key: \"snakeCase\"\r\n }\r\n },\r\n {\r\n name: \"Capitalize\", description: \"Convert text to Capital Case\", value: {\r\n key: \"capitalize\"\r\n }\r\n },\r\n {\r\n name: \"camelCase\", description: \"Convert text to camelCase\", value: {\r\n key: \"camelCase\"\r\n }\r\n },\r\n {\r\n name: \"kebab-case\", description: \"Convert text to kebab-case\", value: {\r\n key: \"kebabCase\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Characters\", description: \"Reverse the characters in the text\", value: {\r\n key: \"reverseCharacters\"\r\n }\r\n },\r\n {\r\n name: \"Remove Duplicate Lines\",\r\n description: \"Remove duplicate lines from the text\",\r\n value: {\r\n key: \"removeDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Keep Only Duplicate Lines\",\r\n description: \"Keep only duplicate lines in the text\",\r\n value: {\r\n key: \"keepOnlyDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove Empty Lines\", description: \"Remove empty lines from the text\", value: {\r\n key: \"removeEmptyLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove All New Lines\", description: \"Remove all new lines from the text\", value: {\r\n key: \"removeAllNewLines\"\r\n }\r\n },\r\n {\r\n name: \"Trim Each Line\",\r\n description: \"Trim whitespace from the beginning and end of each line\",\r\n value: {\r\n key: \"trimEachLine\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Alphabetically\", description: \"Sort lines alphabetically\", value: {\r\n key: \"sortLinesAlphabetically\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Numerically\", description: \"Sort lines numerically\", value: {\r\n key: \"sortLinesNumerically\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Lines\", description: \"Reverse the order of lines\", value: {\r\n key: \"reverseLines\"\r\n }\r\n },\r\n {\r\n name: \"Shuffle Lines\", description: \"Randomly shuffle the order of lines\", value: {\r\n key: \"shuffleLines\"\r\n }\r\n },\r\n {\r\n name: \"Join By\",\r\n description: \"Join lines by a custom separator\",\r\n value: {\r\n key: \"joinBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to join lines\",\r\n defaultValue: \",\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Split By\",\r\n description: \"Split lines by a custom separator\",\r\n value: {\r\n key: \"splitBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to split lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Wrapping\",\r\n description: \"Remove wrapping characters from each line\",\r\n value: {\r\n key: \"removeWrapping\",\r\n },\r\n },\r\n {\r\n name: \"Wrap Each Line With\",\r\n description: \"Wrap each line with a custom character or string\",\r\n value: {\r\n key: \"wrapEachLine\",\r\n parameter: {\r\n name: \"Wrapper\",\r\n description: \"Enter a wrapper for each line\",\r\n defaultValue: '\"',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Capture Each Line\",\r\n description: \"Capture and return the first match of a regex pattern in each line\",\r\n value: {\r\n key: \"captureEachLine\",\r\n parameter: {\r\n name: \"Pattern\",\r\n description: \"Enter a regex pattern to capture\",\r\n defaultValue: \"\\\\d+\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Lines Matching\",\r\n description: \"Remove lines that match the given regex\",\r\n value: {\r\n key: \"removeLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to remove\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Keep Lines Matching\",\r\n description: \"Keep lines that match the given regex\",\r\n value: {\r\n key: \"keepLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to keep\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Prepend Text to All Lines\",\r\n description: \"Add text to the beginning of all lines\",\r\n value: {\r\n key: \"prependTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to prepend to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Append Text to All Lines\",\r\n description: \"Add text to the end of all lines\",\r\n value: {\r\n key: \"appendTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to append to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Replace Regex in All Lines\",\r\n description: \"Replace regex matches in all lines with specified text\",\r\n value: {\r\n key: \"replaceRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex and Replacement\",\r\n description: \"Enter regex and replacement text separated by a '|'\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Generate Numbered List\",\r\n description: \"Prepend numbers to each line\",\r\n value: {\r\n key: \"generateNumberedList\",\r\n },\r\n },\r\n {\r\n name: \"Remove Regex In All Lines\",\r\n description: \"Remove matches of the provided regex in all lines\",\r\n value: {\r\n key: \"removeRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to remove from all lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"No Operation\",\r\n description: \"Do nothing to the text, if you accidentally hit Cmd + enter and need no more transformations\",\r\n }\r\n]\r\n\r\nconst handleTransformation = async (text, transformation) => {\r\n let {key, parameter} = transformation;\r\n let paramValue = parameter ? await arg({\r\n input: parameter.defaultValue,\r\n }, (input) => md(`
`)\r\n } catch (e) {\r\n return '...'\r\n }\r\n },\r\n }\r\n })\r\n )\r\n rerun = flag?.rerun as boolean;\r\n\r\n clipboardText = await handleTransformation(clipboardText, transformation);\r\n operations.push(transformation.key);\r\n}\r\n\r\nawait clipboard.writeText(clipboardText)\r\n\r\nawait notify(\"Text transformation applied and copied to clipboard\")\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1203","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1202","url":"https://github.com/johnlindquist/kit/discussions/1202","title":"Screencapture OCR Script","name":"Screencapture OCR Script","extension":".md","description":"Created by ramiroaraujo","resourcePath":"/johnlindquist/kit/discussions/1202","createdAt":"2023-04-25T01:55:18Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjxD","body":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/ramiroaraujo/d1924947b178742c8cd80f320d5e8e63/raw/07d0c86ff19b503bd856dd731294c4866aea7c79/ocr.ts\")\r\n\r\nOCR script that uses the OS native screencapture to capture part of your screen, perform OCR on it and copy the text to the clipboard.\r\nNote: I haven't even tested Windows and Linux versions. ChatGPT just wrote those for me :)\r\n\r\n```js\r\n// Name: OCR\r\n// Description: Capture a screenshot and recognize the text using tesseract.js\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n//both win and linux implementations were created by chatgpt (gpt4), without _any_ tests!! 😅\r\nconst captureScreenshot = async () => {\r\n const tmpFile = `/tmp/screenshot-${Date.now()}.png`;\r\n\r\n if (isMac) {\r\n await exec(`screencapture -i ${tmpFile}`);\r\n } else if (isWin) {\r\n const psScript = `\r\n Add-Type -AssemblyName System.Windows.Forms\r\n [System.Windows.Forms.SendKeys]::SendWait('%{PRTSC}')\r\n Start-Sleep -m 500\r\n $clipboardData = Get-Clipboard -Format Image\r\n $clipboardData.Save('${tmpFile}', [System.Drawing.Imaging.ImageFormat]::Png)\r\n `;\r\n await exec(`powershell -Command \"${psScript.replace(/\\n/g, '')}\"`);\r\n } else if (isLinux) {\r\n // Check if gnome-screenshot is available\r\n try {\r\n await exec('gnome-screenshot --version');\r\n await exec(`gnome-screenshot -f ${tmpFile}`);\r\n } catch (error) {\r\n // If gnome-screenshot is not available, try using ImageMagick's 'import' command\r\n await exec(`import ${tmpFile}`);\r\n }\r\n }\r\n\r\n return tmpFile;\r\n};\r\n\r\nconst recognizeText = async (filePath, language) => {\r\n const { createWorker } = await npm(\"tesseract.js\");\r\n const worker = await createWorker();\r\n\r\n await worker.loadLanguage(language);\r\n await worker.initialize(language);\r\n\r\n const { data } = await worker.recognize(filePath);\r\n\r\n await worker.terminate();\r\n\r\n return data.text;\r\n};\r\n\r\nconst languages = [\r\n { name: \"Spanish\", value: \"spa\" },\r\n { name: \"French\", value: \"fra\" },\r\n { name: \"Portuguese\", value: \"por\" },\r\n { name: \"English\", value: \"eng\" },\r\n];\r\n//@todo train a model for typescript (https://github.com/tesseract-ocr/tesstrain)\r\n\r\n// if ctrl is pressed, show a modal to select a language\r\nconst selectedLanguage = flag.ctrl\r\n ? await arg(\"Select a language:\", languages)\r\n : \"eng\";\r\n\r\n// Hide the Kit modal before capturing the screenshot\r\nawait hide();\r\n\r\nconst filePath = await captureScreenshot();\r\nif (!await pathExists(filePath)) exit()\r\n\r\nconst text = await recognizeText(filePath, selectedLanguage);\r\n\r\nif (text) {\r\n await clipboard.writeText(text.trim());\r\n await notify(\"Text recognized and copied to clipboard\");\r\n} else {\r\n await notify(\"No text found in the screenshot\");\r\n}\r\n\r\n// Clean up temporary file\r\nawait remove(filePath);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1202","img":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1201","url":"https://github.com/johnlindquist/kit/discussions/1201","title":"Toggle Screen Lock Macos","name":"Toggle Screen Lock Macos","extension":".md","description":"Created by ElTacitos","resourcePath":"/johnlindquist/kit/discussions/1201","createdAt":"2023-04-24T21:34:45Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATjp_","body":"\r\n[Open toggle-screen-lock in Script Kit](https://scriptkit.com/api/new?name=toggle-screen-lock&url=https://gist.githubusercontent.com/ElTacitos/7bb758f516e8e3bc5e1085e306bb0f31/raw/30f26138ddbe17626f1b47c2f2e2c20fa45749b6/toggle-screen-lock.js\")\r\n\r\n```js\r\n// Name: Toggle Screen Lock\r\n// Description: Toggle screen lock on macos (never or 2 minutes)\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet password = await arg({\r\n placeholder: \"Enter sudo password\",\r\n secret: true\r\n})\r\n\r\nconst resp = await exec(`echo ${password} | sudo -S pmset -g | grep displaysleep`)\r\nconst currentSleep = resp.stdout.trimStart().trimEnd().replace( /\\s\\s+/g, ' ' ).split(/\\s/)[1]\r\nconst user = (await exec(`whoami`)).stdout\r\n\r\nif (currentSleep === \"0\") {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 2`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 60`)\r\n await notify(\"Enabled screen lock\")\r\n} else {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 0`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 0`)\r\n await notify(\"Disabled screen lock\")\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1201","img":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/23535629?u=0174ab792e2efe02dfeeddacdf991e267e0a79fe&v=4","user":"Jossdz","author":"Jose Carlos Correa","twitter":"JossDz","discussion":"https://github.com/johnlindquist/kit/discussions/1192","url":"https://github.com/johnlindquist/kit/discussions/1192","title":"Executing teminal commands on WSL(Windows subsystem for Linux)","name":"Executing teminal commands on WSL(Windows subsystem for Linux)","extension":".md","description":"Created by Jossdz","resourcePath":"/johnlindquist/kit/discussions/1192","createdAt":"2023-04-15T23:57:50Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATbHc","body":"Hey Folks,\r\n\r\nI've been working with Script Kit and encountered concerns regarding my local environment. Specifically, since I'm using Linux within Windows through WSL2 (Windows Subsystem for Linux), I was worried about executing the necessary commands for my workflow.\r\n\r\nUpon contacting John, he suggested using the following environment variable to enable command execution in WSL:\r\n\r\n```env\r\n# The value should be the full path to wsl, this is what is needed on windows 11.\r\nKIT_SHELL=C:\\Windows\\System32\\wsl.exe\r\n```\r\n\r\nAfter implementing this variable, I was able to run my commands in WSL. This is particularly useful for me as I can now start my docker environment with a single command.","value":"https://github.com/johnlindquist/kit/discussions/1192","img":"https://avatars.githubusercontent.com/u/23535629?u=0174ab792e2efe02dfeeddacdf991e267e0a79fe&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","author":"Kent C. Dodds","twitter":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1190","url":"https://github.com/johnlindquist/kit/discussions/1190","title":"Extract text from images","name":"Extract text from images","extension":".md","description":"Created by kentcdodds","resourcePath":"/johnlindquist/kit/discussions/1190","createdAt":"2023-04-14T15:23:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATaLp","body":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/kentcdodds/9d49b047079acb3a2e133f7a55fd1837/raw/a7c04475d41460bc8addfa3132f19244691726f3/ocr.ts\")\r\n\r\n```js\r\n// Menu: Optical Character Recognition\r\n// Description: Extract text from images\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport Tesseract from 'tesseract.js'\r\n\r\nconst clipboardImage = await clipboard.readImage()\r\n\r\nif (clipboardImage.byteLength) {\r\n const {data} = await Tesseract.recognize(clipboardImage, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n} else {\r\n let selectedFiles = await getSelectedFile()\r\n let filePaths: Array\r\n\r\n if (selectedFiles) {\r\n filePaths = selectedFiles.split('\\n')\r\n } else {\r\n let droppedFiles = await drop({placeholder: 'Drop images to compress'})\r\n filePaths = droppedFiles.map(file => file.path)\r\n }\r\n for (const filePath of filePaths) {\r\n const {data} = await Tesseract.recognize(filePath, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n }\r\n}\r\n\r\nnotify({\r\n title: 'OCR finished',\r\n message: `Copied text to your clipboard`,\r\n})\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1190","img":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","author":"Kostas Minaidis","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1187","url":"https://github.com/johnlindquist/kit/discussions/1187","title":"Get the price of Bitcoin using the BitFinex open API","name":"Get the price of Bitcoin using the BitFinex open API","extension":".md","description":"Created by kostasx","resourcePath":"/johnlindquist/kit/discussions/1187","createdAt":"2023-04-09T20:58:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATUEu","body":"```js\r\n// Name: BitCoinPrice\r\n// Description: Get latest Bitcoin price using the Bitfinex open API\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet response = await get(`https://api.bitfinex.com/v1/pubticker/BTCUSD`, {\r\n headers: {\r\n Accept: \"text/plain\",\r\n },\r\n})\r\n\r\nconst data = response.data\r\nawait div(`\r\n
\r\n
Price: $${data.last_price}
\r\n
\r\n`)\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1187","img":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","author":"Rohit Kumar Saini","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1183","url":"https://github.com/johnlindquist/kit/discussions/1183","title":"Script to open your links in browser","name":"Script to open your links in browser","extension":".md","description":"Created by rockingrohit9639","resourcePath":"/johnlindquist/kit/discussions/1183","createdAt":"2023-04-04T06:47:52Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATPCd","body":"Hey there,\r\nI have created a script which can open your pre-added links in the browser after selecting from the choices.\r\nFor now, it is just a simple script with hardcoded links, in future we can use `db` to store the links for users.\r\n\r\nHere is the script code -\r\n```js\r\n// Name: Open My Links\r\n// Shortcut: cmd shift l\r\n// Author: Rohit Saini\r\n// GitHub: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst LINKS = {\r\n Github: \"https://github.com/rockingrohit9639\",\r\n LinkedIn: \"https://www.linkedin.com/in/rohit-kumar-saini/\",\r\n} as const;\r\n\r\nconst CHOICES: (keyof typeof LINKS)[] = [\r\n \"Github\",\r\n \"LinkedIn\",\r\n];\r\n\r\nconst linkTitle = await arg(\"Which link to open?\", CHOICES);\r\nconst link = LINKS[linkTitle];\r\nconst command = `open ${link}`;\r\nexec(command);\r\n\r\n```\r\n\r\nHere is the demo of the script - \r\n[link open script.webm](https://user-images.githubusercontent.com/40729749/229710396-04ea1030-354f-4ee3-a08c-c9b2a4d55ca2.webm)\r\n","value":"https://github.com/johnlindquist/kit/discussions/1183","img":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1178","url":"https://github.com/johnlindquist/kit/discussions/1178","title":"Script Kit 1.53.22 - April 2023 Release","name":"Script Kit 1.53.22 - April 2023 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1178","createdAt":"2023-03-25T22:46:01Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ATFov","body":"# Script Kit 1.53.22 - April 2023 Release\r\n\r\n- Download from https://www.scriptkit.com/\r\n- Join our Discord: https://discord.gg/qnUX4XqJQd\r\n- Sponsor to Keep Script Kit Growing ❤️ (and unlock the debugger): [https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205)\r\n\r\n## Features\r\n\r\n### `await mic()`\r\n\r\nUsing await mic will return a buffer of the audio recorded from your microphone.\r\n\r\n```js\r\n// Name: Transcribe Mic\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Configuration, OpenAIApi } = await import(\"openai\")\r\n\r\nlet config = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n})\r\n\r\nlet openai = new OpenAIApi(config)\r\n\r\nlet data = await mic()\r\n\r\nlet stream = Readable.from(data)\r\n// https://github.com/openai/openai-node/issues/77#issuecomment-1463150451\r\nstream.path = \"speech.webm\"\r\n\r\n// If you're confused by \"createTranscription\" params, see: https://github.com/openai/openai-node/issues/93#issuecomment-1471285341\r\nlet response = await openai.createTranscription(stream, \"whisper-1\")\r\n\r\nlet transcriptionPath = tmpPath(`${Date.now()}.txt`)\r\nawait writeFile(transcriptionPath, response.data.text)\r\n\r\nawait editor(response.data.text)\r\n```\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783859/clipboard/pvbsvjjj1zrmlode3wwn.png)\r\n\r\n\r\n### `await webcam()`\r\n\r\nUsing await webcam will return a buffer of the image captured from your webcam.\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679778480/clipboard/lxhy4fmndgzp7xximgco.png)\r\n\r\n```js\r\n// Name: Selfie\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await webcam()\r\n\r\nlet filePath = tmpPath(\"webcam.png\")\r\n\r\nawait writeFile(filePath, buffer)\r\nawait revealFile(filePath)\r\n```\r\n\r\n### Resizing\r\n\r\nThe prompt will now grow and shink to match the length of the list of options.\r\n\r\nYou can also manually control the size of prompts by using the new `PROMPT` constants:\r\n\r\n```js\r\nawait editor({\r\n width: PROMPT.WIDTH[\"5XL\"],\r\n height: PROMPT.HEIGHT[\"5XL\"],\r\n})\r\n```\r\n\r\n### Terminal \"Close on Exit\"\r\n\r\nThe terminal will now automatically close when you exit the command.\r\n\r\n> Note: By default, it start a shell (zsh/bash/cmd.exe for mac/linux/windows), so you'll need to run `&& exit` after your command\r\n\r\n```js\r\nawait term(\"brew install ffmpeg && exit\")\r\n```\r\n\r\nYou can also use the `shell` option to disable the shell and just run the tool directly:\r\n\r\n```js\r\nawait term({\r\n shell: false,\r\n command: \"brew\",\r\n args: [\"services\", \"restart\", \"yabai\"],\r\n closeOnExit: false, // optional, if you want to view output before it closes\r\n})\r\n```\r\n\r\n> Note: Also fixed some resizing bugs with the terminal.\r\n\r\n### Windows App Launcher\r\n\r\nPress `;` from the main menu to launch the App Launcher.\r\n\r\nThanks to @dodgez: https://github.com/johnlindquist/kit/pull/1161\r\n\r\n> Note: We still need a Linux App Launcher if anyone wants to take a stab at it :)\r\n\r\n### Custom Fonts\r\n\r\nYou can now use custom fonts with the UI. In your `~/.kenv/.env` file, add:\r\n\r\n```bash\r\nKIT_MONO_FONT=\"Menlo\"\r\nKIT_SANS_FONT=\"Arial\"\r\n```\r\n\r\n### Experimental: Shebang Scripts 🎉\r\n\r\nAdd a script in your `~/.kenv/scripts` directory with a shebang:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679779537/clipboard/v1oja8v05dgexdgtfjba.png)\r\n\r\n```bash\r\n#!/bin/bash\r\nopen \"https://scriptkit.com\"\r\n```\r\n\r\nThen run it from the main menu\r\n\r\n### Experimental: Windows `.bat` Files\r\n\r\nMac and Linux users have always had executables they could run in the terminal in the `~/.kenv/bin` dircetory. Now, on Windows, Script Kit will generate `.bat` files associated with each script that you can run from the terminal.\r\n\r\n### Info Choices\r\n\r\nWe now have a custom \"info\" choice that isn't selectable, but shows up in the list to present the user with additional information about the input/list:\r\n\r\n![http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png](http://res.cloudinary.com/johnlindquist/image/upload/v1679783784/clipboard/j4av6h2ybe1vezln2tt3.png)\r\n\r\n```js\r\n// Name: Testing Info OnNoChoices\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\"Select a number\",\r\n [\r\n {\r\n name: \"Sorry, no matches\",\r\n info: \"onNoChoices\", // or \"always\"\r\n },\r\n \"one\",\r\n \"two\",\r\n \"three\",\r\n ]\r\n)\r\n```\r\n\r\n### Removing `npm` requirement\r\n\r\nWhen you run a script with a missing module, it will catch the error and prompt you to install it. This removes the requirement of the `await npm()`, but you can still use it if you find it more convenient.\r\n\r\nThe examples and scripts that I share from now on will no longer use `await npm()`, but I have no plans to deprecate it.\r\n\r\n## Fixes\r\n\r\n- Lots of little improvements around the `chat` component.\r\n- Some solid performance improvements\r\n- Upgrading to lots of the latest libraries\r\n\r\n\r\n## Call for Help: Disable Window Animation on Windows?\r\n\r\nIf anyone knows how to disable the window animation on Windows (the one that transparently zooms in when you open a window), please let me know because I think it's super annoying and I'd love to disable it in the app. For now, I strongly recommend disabling it globally on Windows:\r\n\r\n1. Open the Settings app by pressing the Windows key + I.\r\n2. Click on “Ease of Access”.\r\n3. Scroll down to “Other options” and click on “Visual options”.\r\n4. Under “Play animations in Windows”, toggle the switch to “Off”.\r\n\r\n## One Last Thing...\r\n\r\nScript Kit AI course part 1 dropping this week... 💥","value":"https://github.com/johnlindquist/kit/discussions/1178","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1176","url":"https://github.com/johnlindquist/kit/discussions/1176","title":"Force paste into inputs that don't allow it","name":"Force paste into inputs that don't allow it","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1176","createdAt":"2023-03-24T20:59:24Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATFDT","body":"\r\n[Open force-paste in Script Kit](https://scriptkit.com/api/new?name=force-paste&url=https://gist.githubusercontent.com/trevor-atlas/79a688107d6a3362e23adc58f4cce6ed/raw/5d195be171e8356389bd03ce3c9f55109a3fa7ef/force-paste.ts\")\r\n\r\n```js\r\n// Name: force paste\r\n// Description: Paste the contents of your clipboard, even in fields that wouldn't let you paste\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// test it out on the email field here: https://codepen.io/andersschmidt/pen/kOOMmw\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\nawait applescript(`tell application \"System Events\" to keystroke the clipboard as text`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1176","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1173","url":"https://github.com/johnlindquist/kit/discussions/1173","title":"Paste Clipboard Image as Cloudinary Markdown URL","name":"Paste Clipboard Image as Cloudinary Markdown URL","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1173","createdAt":"2023-03-21T17:26:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ATB8T","body":"\r\n[Open paste-image-as-url in Script Kit](https://scriptkit.com/api/new?name=paste-image-as-url&url=https://gist.githubusercontent.com/johnlindquist/3593a0bee037b38c23d216191c4e5d7e/raw/b203b5a826d597ffa9e158db06b1ae6757222569/paste-image-as-url.js\")\r\n\r\n```js\r\n// Name: Paste Clipboard Image as Cloudinary Markdown URL\r\n// Shortcut: opt shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await clipboard.readImage()\r\n\r\nif (buffer && buffer.length) {\r\n let { default: cloudinary } = await npm(\"cloudinary\")\r\n\r\n cloudinary.config({\r\n cloud_name: await env(\"CLOUDINARY_CLOUD_NAME\"),\r\n api_key: await env(\"CLOUDINARY_API_KEY\"),\r\n api_secret: await env(\"CLOUDINARY_API_SECRET\"),\r\n })\r\n\r\n let response = await new Promise((response, reject) => {\r\n let cloudStream = cloudinary.v2.uploader.upload_stream(\r\n {\r\n folder: \"clipboard\",\r\n },\r\n (error, result) => {\r\n if (error) {\r\n reject(error)\r\n } else {\r\n response(result)\r\n }\r\n }\r\n )\r\n\r\n new Readable({\r\n read() {\r\n this.push(buffer)\r\n this.push(null)\r\n },\r\n }).pipe(cloudStream)\r\n })\r\n\r\n log(response)\r\n\r\n // format however you want\r\n let markdown = `![${response.url}](${response.url})`\r\n await setSelectedText(markdown)\r\n} else {\r\n await div(md(`# No Image in Clipboard`))\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1173","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","author":"Kostas Minaidis","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1171","url":"https://github.com/johnlindquist/kit/discussions/1171","title":"Find Duplicate Files","name":"Find Duplicate Files","extension":".md","description":"Created by kostasx","resourcePath":"/johnlindquist/kit/discussions/1171","createdAt":"2023-03-18T21:42:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS_R2","body":"**Find duplicate files in a folder (first-level only) using MD5 hash:**\r\n\r\n```js\r\n// Name: FindDuplicate\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n// Supports: Mac\r\nimport \"@johnlindquist/kit\"\r\nimport fs from \"fs\"\r\nimport crypto from \"crypto\"\r\n\r\nconst folder = await drop();\r\nconst dir = await readdir(folder[0].path)\r\nlet content = `\r\n| Filename | MD5 Hash |\r\n| -------- | -------- |\r\n`;\r\nconst hashes = {}\r\ndir.forEach(file => {\r\n const fullPath = `${folder[0].path}/${file}`\r\n\r\n const stats = fs.statSync(fullPath);\r\n if (stats.isDirectory()) { return; }\r\n\r\n const fileData = fs.readFileSync(fullPath)\r\n const hash = crypto.createHash('md5').update(fileData).digest('hex')\r\n if (hashes[hash]) {\r\n return hashes[hash].push(file)\r\n } \r\n hashes[hash] = [file]\r\n})\r\n\r\nObject.entries(hashes).forEach(([hash, listOfFiles]) => {\r\n if (listOfFiles.length > 1) {\r\n listOfFiles.forEach(file => {\r\n content += `| ${file} | ${hash.slice(0,4) + \"...\" + hash.slice(-4)} |\\n`\r\n })\r\n }\r\n})\r\n\r\nawait div(md(content))\r\n\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1171","img":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","author":"Trevor Atlas","twitter":"trevoratlas","discussion":"https://github.com/johnlindquist/kit/discussions/1169","url":"https://github.com/johnlindquist/kit/discussions/1169","title":"Get a random icebreaker question","name":"Get a random icebreaker question","extension":".md","description":"Created by trevor-atlas","resourcePath":"/johnlindquist/kit/discussions/1169","createdAt":"2023-03-17T20:23:41Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS-vH","body":"\r\n[Open icebreaker in Script Kit](https://scriptkit.com/api/new?name=icebreaker&url=https://gist.githubusercontent.com/trevor-atlas/5eea582ea68faf7a4aa68d1f6ee487bd/raw/9acdba7e0276a9aef96f608dde594f445897e013/icebreaker.ts\")\r\n\r\n```js\r\n// Menu: Icebreaker\r\n// Description: Get a random icebreaker question\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst dbvalues = await db('icebreakers');\r\nconst icebreakers: string[] = dbvalues.data;\r\n\r\nconst getRandomElement = (arr: T[]) => {\r\n const index = Math.floor(Math.random() * arr.length);\r\n return arr[index];\r\n};\r\n\r\nconst item = getRandomElement(icebreakers);\r\n\r\nawait div(\r\n `\r\n
\r\n
${item}
\r\n
\r\n `\r\n);\r\n\r\n```\r\n\r\n\r\nAdd a `json` array of icebreaker questions in the `kenv` `db` folder called `icebreakers.json`\r\nFor example\r\n```json\r\n[\r\n \"Show us the weirdest thing you have in the room with you right now.\",\r\n \"There is a free, round-trip shuttle to Mars. The catch: it will take one year of your life to go, visit, and come back. Are you in?\",\r\n \"What is your least favorite thing about technology?\",\r\n \"What superpower would you most want?\",\r\n \"What food is best with cheese?\",\r\n \"Would you go in the mother-ship with aliens if they landed on Earth tomorrow?\",\r\n \"Would you join a community in space if it was permanent?\",\r\n \"Would you rather live 100 years in the past or 100 years in the future?\",\r\n \"You are the best criminal mastermind in the world. What crime would you commit if you knew you would get away with it?\",\r\n \"You can only eat one food again for the rest of your life. What is it?\",\r\n \"You can visit any fictional time or place. Which would you pick?\",\r\n \"In your time as a student in K-12, what made an impact on you. Not who, but what? What do you remember that influenced you today?\",\r\n \"How would you hide a giraffe from the government?\",\r\n \"If you were an inanimate object, what would you be and why?\",\r\n \"What is the most trivial thing about which you have a strong opinion?\",\r\n \"What is the smallest thing for which you are grateful?\",\r\n \"If you could change one thing about yourself physically, what would you change?\",\r\n \"What single event or decision do you think most affected the rest of your life?\",\r\n \"What do you fear, despite having no real reason to do so? Basically, what is an irrational fear you have?\",\r\n \"Do you have any conspiracy theories? If so, what are they?\",\r\n \"What scientific or technological advance blows your mind? Is there any technology that seems so futuristic and advanced you're surprised it actually exists?\",\r\n \"What is something you don't realise is weird until you really think about it?\",\r\n \"You can transport one furious elephant into any point in history, where would you put it?\",\r\n \"If you could make one thing that is now legal, illegal, and one thing that is illegal, legal, what laws would change?\",\r\n \"Would you agree to go without showering, brushing your teeth, and using deodorant for six months to win $500,000? You are not allowed to talk about the deal with anyone until the six months end, or the offer is gone.\",\r\n \"What's the best trip (traveling wise) you ever had?\",\r\n \"Does pineapple go on pizza?\",\r\n \"If you could live anywhere in the world for a year, where would it be?\",\r\n \"What's your favorite seat on an airplane?\",\r\n \"What is your spirit animal? (The animal who is most similar to your personality.)\",\r\n \"What is your favorite thing to do by yourself?\",\r\n \"Have you ever experienced a natural disaster like a hurricane or tornado?\",\r\n \"If you had to delete all but 3 apps from your smartphone, which ones would you keep? (Three apps that have changed your life.)\",\r\n \"If you had to choose between only having a cell phone or a car for the rest of your life, which would you choose?\",\r\n \"What is your favorite tv series?\",\r\n \"What is your favorite book?\",\r\n \"How would you change your life today if the average life expectancy was 400 years?\",\r\n \"A genie grants you three wishes but none of them can directly benefit you. What would those wishes be?\",\r\n \"What is your favorite smell and why?\",\r\n \"According to you, what is the most mind-numbingly dull movie ever made?\",\r\n \"If given the choice of having a talk show host narrate your life, who would you choose?\",\r\n \"Which reality TV show is your guilty pleasure?\",\r\n \"All in all, the movie that had the most significant impact on your life and why?\",\r\n \"If you could switch your life with any fictional character, who would it be?\",\r\n \"Decidedly, you must choose a fictional world that'll become the new reality. Which one would you pick?\",\r\n \"According to you, what is the most monotonous sport to watch?\",\r\n \"Who would be the first celebrity guest in your very own talk show?\",\r\n \"Without a doubt, who is the greatest actor that has ever graced the world?\",\r\n \"If you had the chance to be in the Olympics, which sport would you compete in?\",\r\n \"Generally, which real life person are you most inspired by?\",\r\n \"What's the most underrated actor that you know of?\",\r\n \"What is your 'I wish I had started doing this earlier in my life'?\",\r\n \"What is the coolest website you've ever visited?\",\r\n \"What is your favorite polite insult?\"\r\n]\r\n```\r\n\r\nUse the script!\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1169","img":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1164","url":"https://github.com/johnlindquist/kit/discussions/1164","title":"A script that asks you to enter a url. it opens it, and takes a small screenshot of it.","name":"A script that asks you to enter a url. it opens it, and takes a small screenshot of it.","extension":".md","description":"Created by SimplGy","resourcePath":"/johnlindquist/kit/discussions/1164","createdAt":"2023-03-12T01:24:50Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS5Nk","body":"\r\n[Open screenshot-url in Script Kit](https://scriptkit.com/api/new?name=screenshot-url&url=https://gist.githubusercontent.com/SimplGy/0e89bc0a60548b32cac9c0db806d9cd4/raw/149f2b5018177cc777bed207b9dc89f664fe54d8/screenshot-url.ts\")\r\n\r\n```js\r\n// Name: Screenshot URL\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\n// get URL from user\r\nlet urlFromUser = await arg(\"Enter the URL to screenshot\");\r\nif (!urlFromUser.match(/^https?:\\/\\//)) {\r\n urlFromUser = `http://${urlFromUser}`;\r\n}\r\nconst pathObj = path.parse(urlFromUser);\r\nlog(pathObj);\r\n\r\n// config\r\nlet timeout = 5_000;\r\nconst FOLDER = 'Downloads/screenshot-url';\r\nconst screenshotFolder = home(FOLDER);\r\nconst filename = `${pathObj.name}${pathObj.ext}.png`\r\nconst screenshotPath = home(FOLDER, filename);\r\n\r\n// Open the window\r\nconst browser = await chromium.launch({ timeout, headless: false });\r\nconst context = await browser.newContext({ colorScheme: \"dark\" });\r\nconst page = await context.newPage();\r\nawait page.setViewportSize({\r\n width: 800,\r\n height: 600,\r\n});\r\npage.setDefaultTimeout(timeout);\r\n\r\ntry {\r\n // docs: https://playwright.dev/docs/api/class-page\r\n await page.goto(urlFromUser);\r\n await page.screenshot({ path: screenshotPath })\r\n \r\n // TODO: shrink the file to a thumbnail\r\n\r\n await revealFile(screenshotFolder)\r\n log(`Done`)\r\n\r\n} catch (error) {\r\n warn('error', error);\r\n}\r\n\r\nawait browser.close();\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1164","img":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/127610787?u=b01a40872f605669b97bb3f31c0a622988ccd019&v=4","user":"laura-ok","author":"Laura Okamoto","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1163","url":"https://github.com/johnlindquist/kit/discussions/1163","title":"natural language shell command","name":"natural language shell command","extension":".md","description":"Created by laura-ok","resourcePath":"/johnlindquist/kit/discussions/1163","createdAt":"2023-03-11T15:02:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS4_I","body":"\r\n[Open natural-language-shell-command in Script Kit](https://scriptkit.com/api/new?name=natural-language-shell-command&url=https://gist.githubusercontent.com/laura-ok/f2cc8d4cfb1211ffc7a494e8f89fff80/raw/04e765fc3b70d0705e874ed62ee16125798394b0/natural-language-shell-command.js\")\r\n\r\n```js\r\n// Name: Natural Language Shell Command\r\n// Description: Convert a natural language command to a shell command\r\n// Author: Laura Okamoto\r\n// Twitter: @laura_okamoto\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { Configuration, OpenAIApi } = await npm(\"openai\");\r\n\r\nconst configuration = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n});\r\nconst openAI = new OpenAIApi(configuration);\r\n\r\nconst res = await arg(\"Describe the shell command you want to run\");\r\nconst prompt = `Use the following shell command to \"${res}\":`;\r\nconst completion = await openAI.createCompletion({\r\n model: \"text-davinci-003\",\r\n prompt,\r\n temperature: 0,\r\n max_tokens: 4069,\r\n});\r\n\r\nsetSelectedText(completion.data.choices[0].text.trim());\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1163","img":"https://avatars.githubusercontent.com/u/127610787?u=b01a40872f605669b97bb3f31c0a622988ccd019&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","author":"Josh Davenport-Smith","twitter":"joshdprts","discussion":"https://github.com/johnlindquist/kit/discussions/1162","url":"https://github.com/johnlindquist/kit/discussions/1162","title":"Preview CSS Color","name":"Preview CSS Color","extension":".md","description":"Created by joshdavenport","resourcePath":"/johnlindquist/kit/discussions/1162","createdAt":"2023-03-11T12:24:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS46m","body":"![image](https://user-images.githubusercontent.com/757828/224484149-4376fcf8-bce5-4adb-a2e0-bb0e9389a42a.png)\r\n\r\n[Open preview-css-color in Script Kit](https://scriptkit.com/api/new?name=preview-css-color&url=https://gist.githubusercontent.com/joshdavenport/86cb857671226ea6fb530c6bd7923bdf/raw/8ffbbe2c3ca55d72a54233d39c88095d338adade/preview-css-color.ts\")\r\n\r\n```js\r\n// Name: Preview CSS Color\r\n// Description: Preview any CSS color accepted by background-color\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst color = await arg(\"Color\");\r\n\r\nawait div(`\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
${color}
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1162","img":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","author":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1160","url":"https://github.com/johnlindquist/kit/discussions/1160","title":"Units Convert","name":"Units Convert","extension":".md","description":"Created by Vedinsoh","resourcePath":"/johnlindquist/kit/discussions/1160","createdAt":"2023-03-07T18:09:07Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS1sV","body":"\r\n[Open units-convert in Script Kit](https://scriptkit.com/api/new?name=units-convert&url=https://gist.githubusercontent.com/Vedinsoh/40e74c82f688a80849da32afde7a5130/raw/2004a082fc3a3142de9ae8510fd42569713ae50e/units-convert.js\")\r\n\r\n```js\r\n// Name: Units Convert\r\n// Description: Convert between metric and imperial units\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst convert = await npm(\"convert-units\");\r\n\r\nconst getAllPossibilities = (unit) => {\r\n const possibilities = unit\r\n ? convert().from(unit).possibilities()\r\n : convert().possibilities();\r\n\r\n return possibilities\r\n .map((u) => {\r\n const uDetails = convert().describe(u);\r\n return {\r\n name: `${u} - ${uDetails.plural}`,\r\n value: u,\r\n };\r\n })\r\n .sort((a, b) => {\r\n const aDetails = convert().describe(a.value);\r\n const bDetails = convert().describe(b.value);\r\n if (aDetails.system === bDetails.system) {\r\n return aDetails.value - bDetails.value;\r\n }\r\n return aDetails.system - bDetails.system;\r\n });\r\n};\r\n\r\nconst getUnitString = (unit) => {\r\n const unitDetails = convert().describe(unit);\r\n return `${unitDetails.plural} (${unit})`;\r\n};\r\n\r\nconst convertUnits = (from, to, amount) => {\r\n return String(convert(amount).from(from).to(to));\r\n};\r\n\r\nconst fromUnit = await arg({\r\n placeholder: \"From\",\r\n choices: getAllPossibilities(),\r\n enter: \"To\",\r\n});\r\n\r\nconst toUnit = await arg({\r\n placeholder: \"To\",\r\n choices: getAllPossibilities(fromUnit),\r\n enter: \"Amount\",\r\n hint: `Convert from ${fromUnit} to...`,\r\n});\r\n\r\nawait arg({\r\n placeholder: \"Amount\",\r\n type: \"number\",\r\n enter: \"Exit\",\r\n hint: `${getUnitString(fromUnit)} equals...`,\r\n onInput: (input) => {\r\n const result = convertUnits(fromUnit, toUnit, input);\r\n setPanel(md(`# ${result} ${getUnitString(toUnit)}`));\r\n },\r\n shortcuts: [\r\n {\r\n name: \"Copy result\",\r\n key: `${cmd}+c`,\r\n onPress: (input) => {\r\n copy(convertUnits(fromUnit, toUnit, input));\r\n },\r\n bar: \"right\",\r\n },\r\n ],\r\n});\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1160","img":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","author":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1158","url":"https://github.com/johnlindquist/kit/discussions/1158","title":"IP & Domain Lookup","name":"IP & Domain Lookup","extension":".md","description":"Created by Vedinsoh","resourcePath":"/johnlindquist/kit/discussions/1158","createdAt":"2023-03-07T15:26:33Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AS1jz","body":"\r\n[Open ip-lookup in Script Kit](https://scriptkit.com/api/new?name=ip-lookup&url=https://gist.githubusercontent.com/Vedinsoh/140a888222f85f8a8da1e65fdbdd87bb/raw/9ad2f7dedacc718ab906fcad75fc43c9c3b05451/ip-lookup.js\")\r\n\r\n```js\r\n// Name: IP & Domain Lookup\r\n// Description: Get information about an IP address or domain\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport net from \"node:net\";\r\nimport { URL } from \"node:url\";\r\n\r\nconst getLookupData = async (query) => {\r\n // Reference: https://ip-api.com/docs/api:json\r\n const response = await get(\r\n `http://ip-api.com/json/${query}?fields=status,message,continent,country,countryCode,regionName,city,zip,lat,lon,timezone,isp,org,as,query`\r\n );\r\n\r\n if (response.data.status === \"fail\") {\r\n throw new Error(response.data.message);\r\n }\r\n\r\n return response.data;\r\n};\r\n\r\nlet lookupQuery = await arg({\r\n placeholder: \"Enter IP address or domain\",\r\n validate: (value) => {\r\n if (net.isIP(value) !== 0) {\r\n return true;\r\n } else {\r\n try {\r\n new URL(`https://${value}`);\r\n return true;\r\n } catch (e) {\r\n return \"Please enter a valid IP address or domain\";\r\n }\r\n }\r\n },\r\n});\r\n\r\nconst data = await getLookupData(lookupQuery);\r\n\r\ndiv(\r\n md(`\r\n# IP Lookup: ${lookupQuery}\r\n\r\n- **IP:** ${data.query}\r\n- **ISP:** ${data.isp}\r\n- **Organization:** ${data.org}\r\n- **AS:** ${data.as}\r\n- **Continent:** ${data.continent}\r\n- **Country:** ${data.country} (${data.countryCode})\r\n- **Region:** ${data.regionName}\r\n- **City:** ${data.city}\r\n- **Zip Code:** ${data.zip}\r\n- **Latitude:** ${data.lat}\r\n- **Longitude:** ${data.lon}\r\n- **Timezone:** ${data.timezone}\r\n`)\r\n);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1158","img":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/4293840?v=4","user":"fischgeek","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1154","url":"https://github.com/johnlindquist/kit/discussions/1154","title":"Simple Snippet Creator","name":"Simple Snippet Creator","extension":".md","description":"Created by fischgeek","resourcePath":"/johnlindquist/kit/discussions/1154","createdAt":"2023-03-05T17:30:25Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASzjM","body":"I wanted a quick way to make very simple text replacements. \r\n\r\n```\r\nlet [txt, rep] = await fields([\"Text\", \"Replacement\"])\r\nlet dir = \"/Users/fischgeek/.kenv/scripts\" // <- Update to your specific path\r\nawait writeFile(`${dir}/${txt}.js`, `//Snippet: ${txt}\\nawait keyboard.type(\"${rep}\")`)\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1154","img":"https://avatars.githubusercontent.com/u/4293840?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/184563?u=27a3324127667282e6eb95d538d5b58b4d007352&v=4","user":"brpaz","author":"Bruno Paz","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1150","url":"https://github.com/johnlindquist/kit/discussions/1150","title":"Raindrop Bookmarks search","name":"Raindrop Bookmarks search","extension":".md","description":"Created by brpaz","resourcePath":"/johnlindquist/kit/discussions/1150","createdAt":"2023-03-04T19:07:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASy0I","body":"Open your [Raindrop](app.raindrop.io/) bookmarks from ScriptKit\r\n\r\n```ts\r\n// Name: Raindrop\r\n// Description: Search your Raindrop.io bookmarks\r\n// Author: Bruno Paz\r\n// Github: @brpaz\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { Choice } from \"@johnlindquist/kit\"\r\n\r\nconst COLLECTION_ID_ALL = 0\r\nconst COLLECTION_ID_UNSORTED = -1\r\n\r\ninterface RaindropResponse {\r\n items: RaindropBookmark[]\r\n}\r\n\r\ninterface RaindropBookmark {\r\n _id: string\r\n title: string\r\n link: string\r\n excerpt: string\r\n tags: string[]\r\n created: string\r\n type: string\r\n}\r\n\r\nconst raindropAPIKey = await env(\"RAINDROP_API_KEY\", {\r\n placeholder: \"Enter your Raindrop.io Test API Key\",\r\n hint: md(\r\n `Get a [Raindrop.io Test API Key](https://app.raindrop.io/settings/integrations)`\r\n ),\r\n\r\n secret: true,\r\n})\r\n\r\nasync function raindropSearch(query: string, collectionId: number): Promise {\r\n const url = `https://api.raindrop.io/rest/v1/raindrops/${collectionId}?search=${query}&access_token=${raindropAPIKey}`\r\n\r\n const response = await get(url)\r\n const data: RaindropResponse = await response.data\r\n\r\n return data.items.map((item) => ({\r\n name: item.title,\r\n description: item.excerpt,\r\n value: item.link,\r\n onSubmit: async () => {\r\n open(item.link)\r\n }\r\n }))\r\n}\r\n\r\nasync function allBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_ALL)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nasync function unsortedBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io unsorted bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_UNSORTED)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nonTab(\"Unsorted\", unsortedBookmarks);\r\nonTab(\"All\", allBookmarks);\r\n``` ","value":"https://github.com/johnlindquist/kit/discussions/1150","img":"https://avatars.githubusercontent.com/u/184563?u=27a3324127667282e6eb95d538d5b58b4d007352&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1148","url":"https://github.com/johnlindquist/kit/discussions/1148","title":"Script Kit v1.51.2 - Welcoming our AI Overlords! 👑","name":"Script Kit v1.51.2 - Welcoming our AI Overlords! 👑","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1148","createdAt":"2023-03-03T19:33:27Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ASyKZ","body":"# Script Kit v1.51.2 - March 2023 Release\r\n\r\nDownload from https://www.scriptkit.com/\r\nJoin our Discord: https://discord.gg/qnUX4XqJQd\r\n❤️ Sponsor Script Kit development [https://github.com/sponsors/johnlindquist](https://github.com/sponsors/johnlindquist/sponsorships?sponsor=johnlindquist&tier_id=235205) ❤️\r\n## Features\r\n\r\n### Build AI Conversations with the New `chat` Prompt\r\n\r\nYou can use the new `chat` prompt to create a chat interface for your scripts. The `chat` prompt returns a `messages` array that you can inspect to see the messages that were sent and received.\r\n\r\nIt supports keyboard navigation so you can press the up/down arrow keys to navigate through the messages and copy/paste them. You can also use `shortcuts` the same way you would with other prompts.\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/609d3f2dcbc49911698c3c2162310c0d/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n```\r\n\r\nThe `kit-examples` repo (bundled with new installs) has an example of using the latest ChatGPT model:\r\nhttps://github.com/johnlindquist/kit-examples/blob/main/scripts/chatgpt.js\r\n\r\n✨ Want more AI example scripts? Check out this post 👀: https://github.com/johnlindquist/kit/discussions/1143 ✨\r\n\r\n### The `eyeDropper` Color Picker\r\n\r\nYou can now use the `eyeDropper` function to pick a color from anywhere on your screen. It returns an object with `sRGBHex` to align with Chrome's Eyedropper tool.\r\n\r\n> Note: Unfortunately, windows restricts color picking to the foreground window. Still investigating a workaround.\r\n\r\n\r\n[Open testing-get-color in Script Kit](https://scriptkit.com/api/new?name=testing-get-color&url=https://gist.githubusercontent.com/johnlindquist/e10cc5dfc08fd5c9824bb3a7e5e50071/raw/76bb4b86f08e1f2a8b2f09d6e2ba47286dc42673/testing-get-color.ts\")\r\n\r\n```js\r\n// Name: Testing Get Color\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide()\r\nlet value = await eyeDropper()\r\nawait editor(value.sRGBHex)\r\n\r\n```\r\n\r\n### Node 18.12.1\r\n\r\nScript Kit now bundles Node 18.12.1. (Previous scripts ran on Node 16.17.2).\r\n\r\n### New Settings in `~/.kit/db/app.json` Features\r\n\r\n- \"mini\" boolean - Use Script Kit in a much smaller window\r\n- \"termFont\" string - Set the font for the terminal\r\n- \"cachePrompt\" boolean - If you experience issues with the prompt position caching logic, disable it so is _always_ show in the center of the screen\r\n- \"convertKeymap\" boolean - Script Kit auto converts to your selected OS keymap (dvorak, colemak, etc). There have been a few reports of this conflicting with Portuguese keyboards. If you experience issues, disable this setting.\r\n- \"searchDebounce\" boolean - by default, Script Kit will debounce when your choices list has over >1000 items. It's mostly a precaution and you can disable it if you'd like.\r\n\r\nThe defaults are as follows:\r\n```json\r\n{\r\n \"mini\": false,\r\n \"termFont\": \"monospace\",\r\n \"cachePrompt\": true,\r\n \"convertKeymap\": true,\r\n \"searchDebounce\": true\r\n}\r\n```\r\n\r\n## Fixes\r\n\r\n- Fixed a bug where `theme` was reset to the default when waking from sleep\r\n- Fixed the `Open in Script Kit` urls for Windows users\r\n- Fixed Calculator sizing from main menu\r\n- `await npm(\"package-name\")` will now mark a package as an \"external\" dependency in TypeScript so you can use import statements for packages that aren't installed yet and your script will still compile\r\n- Other minor bug fixes\r\n- Fixed installation issues when dealing with certs/proxies\r\n\r\n## Experimental\r\n\r\n### `toast()`\r\n\r\nYou can try out the experimental `toast(\"Copied\")` feature. I'm still working out the API, so there's no \"onClick\" or anything, but you should be able to pass the other toast properties (this uses `react-toastify`) explained [here](https://fkhadra.github.io/react-toastify/dispatch-toast-outside-of-react-component)","value":"https://github.com/johnlindquist/kit/discussions/1148","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1143","url":"https://github.com/johnlindquist/kit/discussions/1143","title":"Script Kit AI Chat Pre-release","name":"Script Kit AI Chat Pre-release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1143","createdAt":"2023-02-25T03:07:13Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ASrSe","body":"\r\nhttps://user-images.githubusercontent.com/36073/221332902-87c2d0a4-46b8-4eb1-a935-9a9fcae90e94.mp4\r\n\r\n## This is a Preview Build for Next Week's Release\r\n\r\nI'm releasing this to gather some feedback about the Chat component before I push the main release in the beginning of March. Please let me know below what you think of the new `chat` component. What do you love? What needs to change? What should be added? Please leave your ideas below! 🙏\r\n\r\nDownload here Pre-release here:\r\n\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.50.6\r\n\r\nJoin our Discord:\r\n\r\nhttps://discord.gg/qnUX4XqJQd\r\n\r\n## Chat Component Hello World\r\n\r\n\r\n[Open chat-hello-world in Script Kit](https://scriptkit.com/api/new?name=chat-hello-world&url=https://gist.githubusercontent.com/johnlindquist/0f0f86d0dc84f7acb30dc726da39b470/raw/bfe67e1067554893647fa84ecd8277c00f848926/chat-hello-world.ts\")\r\n\r\n```js\r\n// Name: Chat Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet messages = await chat({\r\n onSubmit: input => {\r\n chat.addMessage(`You said, \"${input}\"`)\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n\r\n## Chat Component with AI\r\n\r\n[Open openai-chat in Script Kit](https://scriptkit.com/api/new?name=openai-chat&url=https://gist.githubusercontent.com/johnlindquist/fc9f74aa387e371a59c973175201ead2/raw/e94fc92dec07dda63982bb54e756a35cfdb92532/openai-chat.ts\")\r\n\r\n```js\r\n// Name: OpenAI Chat\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { ConversationChain } = await import(\"langchain/chains\")\r\nlet { BufferMemory } = await import(\"langchain/memory\")\r\n\r\nlet llm = new OpenAI({\r\n openAIApiKey: await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n }),\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet memory = new BufferMemory()\r\nlet chain = new ConversationChain({\r\n llm,\r\n memory,\r\n})\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n await chain.call({ input })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n## Chat with a .txt File\r\n\r\n[Open doc-talk in Script Kit](https://scriptkit.com/api/new?name=doc-talk&url=https://gist.githubusercontent.com/johnlindquist/f1bfd6758815a1fb87d51a9c7893bd2e/raw/9d54e81aa9f269af0a22a44e6bfcb0f77b04f44d/doc-talk.ts\")\r\n\r\n```js\r\n/*\r\n# Doc Talk\r\n\r\nChat with a .txt file such as a book. In chat, ask questions like:\r\n- \"Who are the main characters?\"\r\n- \"Where are the key settings\"?\r\n- \"Summarize the story arc\"\r\n\r\n> Note: The chat's knowledge is limited to the loaded .txt file\r\n*/\r\n\r\n// Name: Doc Talk\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\nawait npm(\"hnswlib-node\")\r\n\r\nlet { OpenAI } = await import(\"langchain/llms\")\r\nlet { VectorDBQAChain } = await import(\"langchain/chains\")\r\nlet { HNSWLib } = await import(\"langchain/vectorstores\")\r\nlet { OpenAIEmbeddings } = await import(\r\n \"langchain/embeddings\"\r\n)\r\nlet { RecursiveCharacterTextSplitter } = await import(\r\n \"langchain/text_splitter\"\r\n)\r\n\r\nlet filePath = await path({\r\n hint: `Select a .txt file to talk to or Download Romeo and Juliet`,\r\n})\r\n\r\nif (filePath === \"__ROMEO__\") {\r\n let buffer = await download(\r\n `https://www.gutenberg.org/cache/epub/1513/pg1513.txt`\r\n )\r\n filePath = home(\"Downloads\", \"romeo-and-juliet.txt\")\r\n await writeFile(filePath, buffer)\r\n}\r\n\r\nwait(250).then(() => setLoading(true))\r\n\r\ndiv(\r\n md(`# Loading...\r\n\r\n~~~\r\nLoading in ${filePath}\r\n~~~\r\n`)\r\n)\r\n\r\nlet text = await readFile(filePath, \"utf-8\")\r\n\r\nconst model = new OpenAI({\r\n streaming: true,\r\n callbackManager: {\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nconst textSplitter = new RecursiveCharacterTextSplitter({\r\n chunkSize: 1000,\r\n})\r\nconst docs = textSplitter.createDocuments([text])\r\n\r\nconst vectorStore = await HNSWLib.fromDocuments(\r\n docs,\r\n new OpenAIEmbeddings()\r\n)\r\n\r\nconst chain = VectorDBQAChain.fromLLM(model, vectorStore)\r\n\r\nsetLoading(false)\r\nlet messages = await chat({\r\n onSubmit: async query => {\r\n const res = await chain.call({\r\n input_documents: docs,\r\n query,\r\n })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Google AI\r\n\r\n\r\n[Open google-ai in Script Kit](https://scriptkit.com/api/new?name=google-ai&url=https://gist.githubusercontent.com/johnlindquist/3086fd1ddb12e16056cf116633f69547/raw/7ded0692a0909bbdd8b80eb7af69a5d2bf3d7352/google-ai.ts\")\r\n\r\n```js\r\n/*\r\n# Google AI Experiment\r\n\r\nThis is 100% experimental. I'm still learning the ins and outs of langchain.\r\nIf you have feedback on how to improve, PLEASE share 🙏\r\n\r\n\\- John Lindquist\r\n */\r\n\r\n// Name: Google AI\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait npm(\"openai\")\r\nawait npm(\"langchain\")\r\n\r\n// Note: This lib is updated fairly frequently to keep up with Google's changes: https://www.npmjs.com/package/googlethis\r\nawait npm(\"googlethis\")\r\nawait npm(\"@extractus/article-extractor\")\r\n\r\nlet { OpenAI } = await import(\"langchain\")\r\nlet { Tool } = await import(\"langchain/tools\")\r\nlet { initializeAgentExecutor } = await import(\r\n \"langchain/agents\"\r\n)\r\n\r\nclass GoogleThis extends Tool {\r\n name = \"search\"\r\n description =\r\n \"a search engine. useful for when you need to answer questions about current events. input should be a search query. Output should include the best result and associated URL.\"\r\n\r\n formatResults = response => {\r\n let data = response?.results\r\n ?.slice(0, 3)\r\n ?.map(r => {\r\n return `\r\ntitle: ${r.title}\r\ndescription: ${r.description}\r\nurl: ${r.url}\r\n`\r\n })\r\n .join(\"\\n\")\r\n\r\n if (response?.knowledge_panel?.title)\r\n data = `Best title: ${response?.knowledge_panel?.title}\r\n${data}`\r\n\r\n if (response?.knowledge_panel?.description)\r\n data = `Best description: ${response?.knowledge_panel?.description}\r\n${data}`\r\n\r\n return data\r\n }\r\n\r\n async call(input: string) {\r\n let google = await import(\"googlethis\")\r\n let response = await google.search(input)\r\n return this.formatResults(response)\r\n }\r\n}\r\n\r\nclass ReadURL extends Tool {\r\n name = \"read\"\r\n description = `a web scraper. Input is a url. Output is the contents of the page.`\r\n\r\n formatArticle = (url, article) => {\r\n let formatted = ``\r\n try {\r\n formatted = Object.entries(article)\r\n .filter(([key, value]) => key && value)\r\n .map(([key, value]) => {\r\n // In case the article contents are too long\r\n // TODO: Should probably wrap over to a \"Document\" paradigm here...\r\n if (typeof value === \"string\") {\r\n return [key, value?.slice(0, 1000) || \"\"]\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n return [key, value.join(\",\")]\r\n }\r\n\r\n return [key, value]\r\n })\r\n .map(([key, value]) => `${key}: ${value}`)\r\n .join(\"\\n\")\r\n } catch (error) {\r\n formatted = `Couldn't read the contents of ${url}`\r\n }\r\n return formatted\r\n }\r\n\r\n async call(url: string) {\r\n let { extract } = await import(\r\n \"@extractus/article-extractor\"\r\n )\r\n let article = await extract(url)\r\n let formatted = this.formatArticle(url, article)\r\n\r\n return formatted\r\n }\r\n}\r\n\r\nlet tools = [new GoogleThis(), new ReadURL()]\r\n\r\nlet yankAnswer = async output => {\r\n return output?.generations\r\n ?.at(0)\r\n ?.at(0)\r\n ?.text.split(\"\\n\")\r\n ?.at(-1)\r\n .replace(\"Final Answer: \", \"\")\r\n}\r\n\r\nlet llm = new OpenAI({\r\n temperature: 0.7,\r\n streaming: true,\r\n callbackManager: {\r\n handleError: log,\r\n handleEnd: output => {\r\n let answer = yankAnswer(output)\r\n history = `${history}\r\nAI: ${answer}`\r\n },\r\n handleStart: () => {\r\n chat.addMessage(\"\")\r\n },\r\n handleNewToken: token => {\r\n chat.pushToken(token)\r\n },\r\n },\r\n})\r\n\r\nlet executor = await initializeAgentExecutor(\r\n tools,\r\n llm,\r\n \"zero-shot-react-description\"\r\n)\r\n\r\n// TODO: I'm sure this can be _vastly_ improved\r\nlet history = `The AI should always include relevant URLs when possible.\r\nThe AI should use the given information and URLs to explain why it chose the answer.\r\nThe AI should avoid commas by formatting with newlines\r\n\r\n`\r\n\r\nlet messages = await chat({\r\n ignoreBlur: true,\r\n alwaysOnTop: true,\r\n onSubmit: async input => {\r\n history = `${history}\r\nMe: ${input}`\r\n\r\n await executor.call({ input: history })\r\n },\r\n})\r\n\r\ninspect(messages)\r\n\r\n```\r\n\r\n\r\n## Read More about LangChain to Get Started with AI in Script Kit\r\n\r\nhttps://hwchase17.github.io/langchainjs/docs/overview\r\n","value":"https://github.com/johnlindquist/kit/discussions/1143","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/6349395?u=650885c8ac85a967ab8787bfffad65e1206266d1&v=4","user":"abisuq","author":"__","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1142","url":"https://github.com/johnlindquist/kit/discussions/1142","title":"Escape Backticks for copy paste string in javascript","name":"Escape Backticks for copy paste string in javascript","extension":".md","description":"Created by abisuq","resourcePath":"/johnlindquist/kit/discussions/1142","createdAt":"2023-02-24T05:33:20Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASqgw","body":" [Open browse-scriptkit in Script Kit](https://scriptkit.com/api/new?name=browse-scriptkit&url=https://gist.githubusercontent.com/abisuq/a27c130faa4ceb2b582a78b929bde0b2/raw/5d480d52863532d61adbb363fcb217b3f92c2cb8/browse-scriptkit.js\")\r\n\r\n```js\r\n// Name: Escape Backticks\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"Paste text to escape backticks\");\r\nawait copy(text.replace(/`/g, '\\\\`'));\r\n\r\n\r\n\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1142","img":"https://avatars.githubusercontent.com/u/6349395?u=650885c8ac85a967ab8787bfffad65e1206266d1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/60891163?u=b1985aac6c4755041fd849428360e69819e2339c&v=4","user":"AloisCRR","author":"Alois Carrera","twitter":"AloisCRR","discussion":"https://github.com/johnlindquist/kit/discussions/1141","url":"https://github.com/johnlindquist/kit/discussions/1141","title":"Center focused app based on window dimensions","name":"Center focused app based on window dimensions","extension":".md","description":"Created by AloisCRR","resourcePath":"/johnlindquist/kit/discussions/1141","createdAt":"2023-02-23T13:54:56Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASp0i","body":"\r\n[Open center-app in Script Kit](https://scriptkit.com/api/new?name=center-app&url=https://gist.githubusercontent.com/AloisCRR/4a27c9e04b145be6a32e6ac4fa07894c/raw/f01bffb81564899dc01d7c931d82e072ec1918ba/center-app.js\")\r\n\r\n```js\r\n// Menu: Center App\r\n// Description: Center current focused app based on window size\r\n// Author: Alois Carrera\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst activeScreen = await getActiveScreen()\r\n\r\nconst {\r\n workArea: {\r\n height,\r\n width,\r\n x: workAreaX,\r\n y: workAreaY\r\n }\r\n} = activeScreen\r\n\r\nconst activeAppBounds = await getActiveAppBounds()\r\n\r\nconst { top, left, right, bottom } = activeAppBounds\r\n\r\nconst windowHeight = bottom - top\r\n\r\nconst windowYCenter = windowHeight / 2\r\n\r\nconst windowWidth = right - left\r\n\r\nconst windowXCenter = windowWidth / 2\r\n\r\nsetActiveAppPosition({\r\n x: workAreaX + (width / 2) - windowXCenter,\r\n y: workAreaY + (height / 2) - windowYCenter\r\n})\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1141","img":"https://avatars.githubusercontent.com/u/60891163?u=b1985aac6c4755041fd849428360e69819e2339c&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/830800?v=4","user":"johtso","author":"Johannes","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1126","url":"https://github.com/johnlindquist/kit/discussions/1126","title":"Open daily note in Obsidian","name":"Open daily note in Obsidian","extension":".md","description":"Created by johtso","resourcePath":"/johnlindquist/kit/discussions/1126","createdAt":"2023-02-13T23:20:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AShVy","body":"\r\n[Open daily-note in Script Kit](https://scriptkit.com/api/new?name=daily-note&url=https://gist.githubusercontent.com/johtso/b6a5d6e85d0805dbd25d5a36ffda6abb/raw/ef858f9129001cf187d0eb718108f7d734e2cef6/daily-note.ts\")\r\n\r\n```js\r\n// Name: daily note\r\n// Description: Open today's daily note in obsidian\r\n\r\n// You must install the Actions URI plugin and have the daily notes plugin enabled\r\n// Currently MacOS only\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { homedir } from \"os\"\r\nimport { join as joinPath } from \"path\"\r\n\r\nconst VAULT_NAME = await env(\r\n \"VAULT_NAME\",\r\n async () => {\r\n const vaultNames = await getVaultNames().catch(() => [])\r\n if (vaultNames.length === 1) {\r\n return vaultNames[0]\r\n } else {\r\n return await arg(\r\n \"Which vault do you want to use?\",\r\n vaultNames\r\n )\r\n }\r\n }\r\n);\r\n\r\nconst CREATE_URI = `obsidian://actions-uri/daily-note/create?vault=${VAULT_NAME}&silent=true`\r\nconst OPEN_URI = `obsidian://actions-uri/daily-note/open-current?vault=${VAULT_NAME}`\r\n\r\nawait applescript(`\r\n tell application \"Obsidian\"\r\n open location \"${CREATE_URI}\"\r\n open location \"${OPEN_URI}\"\r\n activate\r\n end tell\r\n`);\r\n\r\nasync function getVaultNames() {\r\n const obsidianConfPath = joinPath(homedir(), \"Library/Application Support/obsidian/obsidian.json\")\r\n\r\n // {\"vaults\":{\"9aeaa3aaa2ad0602\":{\"path\":\"/Users/human/Documents/Obsidian Vault\",\"ts\":1651412412801,\"open\":true}}}\r\n const obsidianConf = JSON.parse(await (await readFile(obsidianConfPath)).toString())\r\n const vaults = obsidianConf.vaults\r\n const vaultNames = Object.keys(vaults).map((vaultId) => vaults[vaultId].path.split(\"/\").pop())\r\n return vaultNames\r\n}\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1126","img":"https://avatars.githubusercontent.com/u/830800?v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1122","url":"https://github.com/johnlindquist/kit/discussions/1122","title":"Anime Search - Working - Updated","name":"Anime Search - Working - Updated","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1122","createdAt":"2023-02-13T01:47:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASgVr","body":"\r\n[Open anime-search in Script Kit](https://scriptkit.com/api/new?name=anime-search&url=https://gist.githubusercontent.com/Ambushfall/c27b170000791f197fcfa5ca154a966b/raw/8bd65a0da0841f83ba892aaa4e3e72649f4283ee/anime-search.js\")\r\n\r\nUpdated Johns amazing script to work with the new v4 Api, and using widget instead of the deprecated showImage method.\r\n\r\nProps to John for making all of this possible!\r\n\r\n[Original script](https://www.scriptkit.com/johnlindquist/anime-search)\r\n\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Menu: Search Anime\r\n// Description: Use the jikan.moe API to search anime\r\n// Author: John Lindquist, Updated by Ambushfall\r\n\r\nlet anime = await arg(\"Anime:\")\r\n\r\nlet response = await get(\r\n `https://api.jikan.moe/v4/anime?q=${anime}`\r\n)\r\n\r\nlet { images, title } = response.data.data[0]\r\n\r\nlet { jpg } = images\r\n\r\nlet { image_url, small_image_url, large_image_url } = jpg\r\n\r\nconst html = `\r\n\r\n
\r\n`;\r\n\r\nlet wg = await widget(html, {\r\n state: {\r\n url: large_image_url\r\n }\r\n})\r\n\r\nwg.onResized(async () => {\r\n wg.fit()\r\n})\r\n\r\n// win32 on-click not working so this does nothing really.\r\n\r\n// TODO: When on click starts working change state to the next result\r\n// wg.onClick((event) => event.targetId === \"x\" ? wg.close() : inspect(event.targetId));\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1122","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1121","url":"https://github.com/johnlindquist/kit/discussions/1121","title":"Part 2: Clipboard special history, preview images as well","name":"Part 2: Clipboard special history, preview images as well","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1121","createdAt":"2023-02-13T00:17:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASgS-","body":"\r\n[Open clipboard-history in Script Kit](https://scriptkit.com/api/new?name=clipboard-history&url=https://gist.githubusercontent.com/Ambushfall/444bb14ca4b5268ea855cec8431b7dfc/raw/3fb4c0962b3cfdf2220fd96f71190e4ec1e872e5/clipboard-history.js\")\r\n\r\n```js\r\n// Menu: Clipboard History\r\n// Description: Copy something from the clipboard history\r\n// Shortcut: command shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { history } = await db(\"clipboard-history\")\r\n\r\nlet { value, type } = await arg(\"What to paste?\", () => {\r\n return history.map(({ value, type, timestamp, secret }) => {\r\n return {\r\n type,\r\n name: secret ? value.slice(0, 4).padEnd(10, \"*\") : value,\r\n value: {\r\n value,\r\n type,\r\n },\r\n description: timestamp,\r\n preview:\r\n type === \"image\"\r\n ? md(`![timestamp](${value})`)\r\n : value.includes(\"\\n\")\r\n ? `
${value\r\n .split(\"\\n\")\r\n .map((line) => `
${line}
`)\r\n .join(\"\")}
`\r\n : null,\r\n }\r\n })\r\n})\r\n\r\nif (type === \"image\") {\r\n await copyPathAsImage(value)\r\n await keystroke(\"command v\")\r\n}\r\n\r\nif (type === \"text\") {\r\n await setSelectedText(value)\r\n}\r\n\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1121","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1120","url":"https://github.com/johnlindquist/kit/discussions/1120","title":"Clipboard special history, preview images as well","name":"Clipboard special history, preview images as well","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1120","createdAt":"2023-02-13T00:12:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASgSx","body":"\r\n\r\n\r\n[Open copy-to-clipboard in Script Kit](https://scriptkit.com/api/new?name=copy-to-clipboard&url=https://gist.githubusercontent.com/Ambushfall/db9f545a9099a0a674f14679c862c0c3/raw/f9712e16d1965f16c578ffe4ad0ccb2c1e493086/copy-to-clipboard.js\")\r\n\r\n\r\ncopy-to-clipboard.js\r\n```js\r\n// Menu: Copy to Clipboard\r\n// Description: Save to Clipboard history\r\n// Shortcut: command shift c\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { write, history } = await db(\"clipboard-history\", { history: [{ value: \"\", type: \"\", timestamp: \"\", secret: \"\" }] })\r\n\r\nconst clipboardVal = await clipboard.readText();\r\n\r\n\r\nconst newValue = {\r\n value: clipboardVal,\r\n timestamp: new Date(Date.now()).toLocaleString('en-GB', { timeZone: 'UTC' }),\r\n secret: clipboardVal.includes('secret'),\r\n type: /(http)?s?:?(\\/\\/[^\"']*\\.(?:png|jpg|jpeg|gif|png|svg))/i.test(clipboardVal) ? \"image\" : \"text\"\r\n}\r\n\r\nhistory.push(newValue)\r\n\r\nawait write()\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1120","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1117","url":"https://github.com/johnlindquist/kit/discussions/1117","title":"Today's date as ISO","name":"Today's date as ISO","extension":".md","description":"Created by SimplGy","resourcePath":"/johnlindquist/kit/discussions/1117","createdAt":"2023-02-11T20:44:58Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASfuX","body":"Completely ripped from https://github.com/johnlindquist/kit/discussions/1116 (Thanks Daniel!) I just like the ISO format better.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/SimplGy/2676c4ccad30d9c71f7096422eb5fd44/raw/0da283bff42524450ee9f162c24451e1a8332a47/today-timestamp.ts\")\r\n\r\n```js\r\n// Name: today-timestamp\r\n// Description: inserts today's date in \"ISO\" format 2023-02-11\r\n// Snippet:\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\nconst formatted = `${today.getFullYear()}-${twoDigits(today.getMonth() + 1)}-${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1117","img":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1116","url":"https://github.com/johnlindquist/kit/discussions/1116","title":"Insert current timestamp as YYYY/MM/DD","name":"Insert current timestamp as YYYY/MM/DD","extension":".md","description":"Created by danielo515","resourcePath":"/johnlindquist/kit/discussions/1116","createdAt":"2023-02-11T06:58:35Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASfaI","body":"This snippet is basic, and stupid, but you will be happy to have it around when you need it.\r\nRather than manually input the current today date, you just type `!tday` and you get it.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/danielo515/9356f1af0b642dd23f6cdc188d73d7be/raw/6f8ca2cf7a666762c533bb47773403f210c55f49/today-timestamp.ts\")\r\n\r\n```ts\r\n// Name: today-timestamp\r\n// Description: inserts the today date (not including time) formatted as YYYY/MM/DD\r\n// Snippet: !tday\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\n// Format date to YYYY/MM/DD format\r\nconst formatted = `${today.getFullYear()}/${twoDigits(today.getMonth() + 1 )}/${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1116","img":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1111","url":"https://github.com/johnlindquist/kit/discussions/1111","title":"Ask for user input and transform it to a url encoded string","name":"Ask for user input and transform it to a url encoded string","extension":".md","description":"Created by danielo515","resourcePath":"/johnlindquist/kit/discussions/1111","createdAt":"2023-02-09T15:41:54Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASd4k","body":"Many times I don't want to url encode what I have on the clipboard, but I want to manually type it. This little script is for that.\r\n\r\n[Open url-encode in Script Kit](https://scriptkit.com/api/new?name=url-encode&url=https://gist.githubusercontent.com/danielo515/b2b2576155111a3a8fb73b47da2efac8/raw/cf81906c75996c52064151670cad363a71c7317d/url-encode.ts\")\r\n\r\n```js\r\n// Name: url encode\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"What do you want to encode\");\r\nconst encoded = encodeURIComponent(text)\r\nawait copy(encoded);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1111","img":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/99090177?u=23294c71201154e9389c53cc961811acb9aff635&v=4","user":"wisskirchenj","author":"Jürgen Wißkirchen","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1110","url":"https://github.com/johnlindquist/kit/discussions/1110","title":"Full text search in (java-)files over all my projects && open hit(s) in IDE","name":"Full text search in (java-)files over all my projects && open hit(s) in IDE","extension":".md","description":"Created by wisskirchenj","resourcePath":"/johnlindquist/kit/discussions/1110","createdAt":"2023-02-08T19:39:47Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4AScxi","body":"My typical usecase: I remember that I used some class (e.g. Mockitos InOrde, CsvSource or ExecutorService) in a similar situation or test and start searching in which project that was. My root contains 30+ projects - so sometimes this took me 15+ min or I had to google again, howto use `find -exec` - and even then it's clumsy.. \r\n\r\nNow this is perfect to me: I enter the search string (class, method, ...) and the surrounding context width in hits (the 'n' in grep -n).\r\nThen a div-container shows me all scrollable hits and I can refresh my mind on what I did years ago. \r\nFinally I hit Return and get a Button-List widget with all filenames with hits, where i can click on. This opens the project in IDEA, opens the file with the hit inside this project and even puts the clipboard copied search string by keystroke into IDEA's search dialog, so I can navigate with arrows...\r\nSuper helpful to me. :smile:\r\n\r\nTo reuse, there is a little customization needed, as I hardcoded my projects root and file pattern *.java. \r\nAlso the exclusion of 'z'-starting directories is sure special to me - but all that should be easy to adapt. \r\nAlso, I am more then happy to help, if there's need.\r\n**Note:** Keystrokes in IDEA at end of script, needs accessibility rights. My platform is MacOS.\r\n\r\n```ts\r\n// Shortcut: cmd opt f\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst smallArg = (placeholder: string) => arg({\r\n placeholder: placeholder,\r\n height: 100,\r\n width: 500\r\n});\r\n\r\nconst substring = await smallArg(\"Substring to search:\");\r\nconst lines = await smallArg(\"# surrounding lines in results:\");\r\n\r\nconst PROJECT_ROOT = \"/Users/jwisskirchen/IdeaProjects\";\r\nconst CLOSE_LABEL = \"Close\";\r\n\r\n// execute find java-files on all project-subdirs not starting with 'z' (as those are special ones...)\r\nconst results = await $`cd ${PROJECT_ROOT} ; find [^z]* -name *.java -exec grep -q ${substring} {} ';' -exec echo \"******{}******\" ';' -exec grep -${lines} ${substring} {} ';'`;\r\n\r\n// Split filepaths and search results in tokens-array, replace '<' as this confuses html-rendering after span-insertion below\r\nconst tokens = results.toString().replaceAll('<', '<').split('******');\r\n\r\n// build templates from tokens with filepath header and search results in
\r\nconst templates = [];\r\nconst files: string[] = [];\r\nfor (let i = 0; i < tokens.length - 1; i += 2) {\r\n files.push(`${tokens[i + 1]}`);\r\n\r\n // mark substrings in red.\r\n templates.push(`
${tokens[i + 1]}
\r\n
${tokens[i + 2]}
`\r\n .replaceAll(`${substring}`,\r\n `${substring}`)\r\n );\r\n}\r\n\r\n// show the templates => user can scroll in results and then press to continue to dialog for opening project file in IDE\r\nawait div({\r\n html: templates.join('
\\n'),\r\n width: 1200,\r\n height: 700\r\n}, `bg-white text-black text-sm p-2`);\r\n\r\n// put search string in clipboard for use in IDE later\r\nawait copy(substring);\r\n\r\n//---- display buttons in widgets, that let you open IntelliJ Idea -----\r\nconst items = files.map(path => ({\r\n name: path,\r\n // display only shrinked filepath /../ for brevity\r\n display: path.slice(0, path.indexOf('/') + 1) + '..' + path.slice(path.lastIndexOf('/'), path.length)\r\n}));\r\nitems.push({ name: CLOSE_LABEL, display: CLOSE_LABEL });\r\n\r\nconst buttons = `\r\n
\r\n \r\n \r\n
\r\n `;\r\n\r\nlet w = await widget(buttons, {\r\n backgroundColor: '#CCCCAA',\r\n x: 600,\r\n y: Math.max(0, 500 - items.length * 25),\r\n width: 600,\r\n height: items.length * 50 + 50,\r\n state: {\r\n items,\r\n }\r\n});\r\n\r\nw.onClick(async event => {\r\n if (event.dataset.name) {\r\n const path: string = event.dataset.name;\r\n\r\n if (path === CLOSE_LABEL) {\r\n w.close();\r\n exit(0); // process keeps running without..\r\n } else {\r\n // open the project in IntelliJ IDEA\r\n await $`idea ${PROJECT_ROOT}/${path.slice(0, path.indexOf('/'))}`;\r\n // open the specific file chosen inside this project\r\n await $`idea ${PROJECT_ROOT}/${path}`;\r\n // inside IDEA (!) do a search Cmd-F for the substring \r\n // Cmd-V places the substring from Clipboard (where we copied it above) into Ideas Search dialog\r\n await hide();\r\n await keyboard.pressKey(Key.LeftSuper, Key.F);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.F);\r\n await keyboard.pressKey(Key.LeftSuper, Key.V);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.V);\r\n }\r\n }\r\n});```","value":"https://github.com/johnlindquist/kit/discussions/1110","img":"https://avatars.githubusercontent.com/u/99090177?u=23294c71201154e9389c53cc961811acb9aff635&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4","user":"mmustra","author":"Marin Muštra","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1097","url":"https://github.com/johnlindquist/kit/discussions/1097","title":"Normalize GIT branch name","name":"Normalize GIT branch name","extension":".md","description":"Created by mmustra","resourcePath":"/johnlindquist/kit/discussions/1097","createdAt":"2023-02-03T16:21:17Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASZBd","body":"\r\n[Open normalize-git-branch-name in Script Kit](https://scriptkit.com/api/new?name=normalize-git-branch-name&url=https://gist.githubusercontent.com/mmustra/73168d0f629f8b2f526fa45d5068ac12/raw/416c129718d1f9641f6212ba530ab7d6b1f92c06/normalize-git-branch-name.js\")\r\n\r\n```js\r\n// Name: Normalize GIT branch name\r\n// Description: Copy text and paste it to normalized GIT branch name\r\n// Author: Marin Muštra\r\n// LinkedIn: https://www.linkedin.com/in/marin-mustra\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst delimiterChar = '-';\r\nconst illegalChar = '';\r\nconst mergableChars = [delimiterChar, illegalChar];\r\nconst shouldLowerCase = true;\r\n\r\nconst input = await paste();\r\nlet branchName = '';\r\n\r\n// Sanitize references\r\n// https://github.com/microsoft/vscode/blob/main/extensions/git/src/commands.ts#L2182\r\n// https://github.com/gitextensions/gitextensions/blob/6eab7392839c4d103bad1581fba5eaf6f008d766/GitCommands/Git/GitBranchNameNormaliser.cs\r\nconst getSanitizedInput = () => {\r\n const edgePattern = /^[-\\s]+|[-\\s]+$/g;\r\n const delimiterPattern = /\\s+|_+|-+/g;\r\n const illegalPattern = /^-+|^\\.|\\/\\.|\\.\\.|~|\\^|:|\\/$|\\.lock$|\\.lock\\/|\\\\|\\*|\\?|@{|^@$|\\.$|\\[|\\]$|^\\/|\\/$/g;\r\n\r\n const isInvalidChar =\r\n (!edgePattern.test(delimiterChar) && illegalPattern.test(delimiterChar)) ||\r\n (!edgePattern.test(illegalChar) && illegalPattern.test(illegalChar));\r\n\r\n if (isInvalidChar) {\r\n throw new Error('Invalid delimiter/illegal character!');\r\n }\r\n\r\n let sanitized = input.trim().replace(delimiterPattern, delimiterChar).replace(illegalPattern, illegalChar);\r\n mergableChars?.forEach((char) => char && (sanitized = sanitized.replace(new RegExp(`\\\\${char}+`, 'g'), char)));\r\n sanitized = sanitized.replace(edgePattern, '');\r\n\r\n return shouldLowerCase ? sanitized.toLowerCase() : sanitized;\r\n};\r\n\r\ntry {\r\n branchName = getSanitizedInput();\r\n\r\n if (!branchName) {\r\n throw new Error('Invalid input!');\r\n }\r\n} catch (error) {\r\n const hint = `${error.message}`;\r\n await editor({ hint, input, description: 'ERROR', readOnly: true, lineNumbers: 'on' });\r\n\r\n exit();\r\n}\r\n\r\nawait setSelectedText(branchName);\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1097","img":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1094","url":"https://github.com/johnlindquist/kit/discussions/1094","title":"Silent Mention","name":"Silent Mention","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1094","createdAt":"2023-02-02T23:54:13Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASYbe","body":"Described here: https://twitter.com/peduarte/status/1621187086802980866?s=20&t=a8WakFD64i8W3BPDLIzfWg\r\n\r\n\r\n[Open silent-mention in Script Kit](https://scriptkit.com/api/new?name=silent-mention&url=https://gist.githubusercontent.com/johnlindquist/9e4885f4e3d80aa0e66f47a727f206c4/raw/7892b042f75e78ba434cf9e7671a5fa275a17350/silent-mention.ts\")\r\n\r\n```js\r\n// Name: Silent Mention\r\n// Shortcut: opt x\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet makeSilent = (str: string) =>\r\n str.replace(/[.#@]/g, m => m + \"\\u2060\")\r\n\r\nlet text =\r\n (await getSelectedText()) ||\r\n (await arg(\"Enter text to silent\"))\r\n\r\nlet silentText = makeSilent(text)\r\nawait setSelectedText(silentText)\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/1094","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1093","url":"https://github.com/johnlindquist/kit/discussions/1093","title":"Script Kit v1.48.0 - February 2023 Release","name":"Script Kit v1.48.0 - February 2023 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1093","createdAt":"2023-02-02T21:32:51Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ASYXj","body":"# Script Kit v1.48.0 - February 2023 Release\r\n\r\nDownload Script Kit v1.48.0 from: https://scriptkit.com\r\n\r\nWatch release video:\r\nhttps://www.youtube.com/watch?v=FQg8AL539_M\r\n\r\n## Drag and Drop Widgets\r\n\r\n![CleanShot 2023-02-02 at 14 31 33](https://user-images.githubusercontent.com/36073/216454341-e1aa117f-c238-4a6b-83af-075c463773ab.gif)\r\n\r\n\r\nWidgets now support `onDrop` and `onMouseDown` handlers. `onMouseDown` can be used in conjunction with `startDrag` to create drag and drop widgets as seen below.\r\n\r\nYou can put any sort of logic in the `onDrop` such as processing files using `ffmpeg`, uploading files to a server, etc, etc.\r\n\r\nThe widget `setState` can be used to show files/progress/etc in effect creating tiny application crafted _just for you_ 🤩\r\n\r\n```js\r\nlet files = []\r\n\r\nlet w = await widget(\r\n `
\r\n
Drop Files
\r\n{{file}}\r\n
\r\n`,\r\n {\r\n containerClass: `p-4 h-screen w-screen overflow-auto`,\r\n width: 300,\r\n height: 300,\r\n draggable: false,\r\n state: {\r\n files,\r\n },\r\n }\r\n)\r\n\r\nw.onDrop(event => {\r\n if (event?.dataset?.files) {\r\n files.push(...event.dataset.files)\r\n w.setState({\r\n files,\r\n })\r\n }\r\n})\r\n\r\nw.onMouseDown(event => {\r\n if (event.dataset.file) {\r\n startDrag(event.dataset.file)\r\n }\r\n})\r\n```\r\n\r\n## `// Alias` Metadata\r\n\r\nHave a script you run often? Use `// Alias` to jump that script to the top of the list when that alias is hit:\r\n\r\n```js\r\n// Alias: hi\r\nsay(\"hi\")\r\n```\r\n\r\n## Fixes for Windows/Linux Installs\r\n\r\nAfter a huge influx of users since the beginning of the year on a variety of different systems and setups, we've been able to test and fix most of the issues that have popped up around various setups with a huge focus on the installation process for Script Kit on Windows and Linux.\r\n\r\nIf you're finding that you're having issues installing Script Kit, please see this discussion: https://github.com/johnlindquist/kit/discussions/1052\r\n","value":"https://github.com/johnlindquist/kit/discussions/1093","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1092","url":"https://github.com/johnlindquist/kit/discussions/1092","title":"Screenshot Current Tweet","name":"Screenshot Current Tweet","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1092","createdAt":"2023-02-02T16:19:30Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASYJl","body":"Focus on a tweet like this:\r\n\r\nhttps://twitter.com/film_girl/status/1621170813796851719\r\n\r\n![1621170813796851719](https://user-images.githubusercontent.com/36073/216381005-74c32641-89e3-416e-af7b-77be619c9c66.png)\r\n\r\nThen run this script for a screenshot:\r\n\r\n\r\n[Open screenshot-current-tweet in Script Kit](https://scriptkit.com/api/new?name=screenshot-current-tweet&url=https://gist.githubusercontent.com/johnlindquist/ced2147f9e918c075e058da4b4c3eb2b/raw/6e46ea8b136c6a0300c69d893b808bb5ad6e80e8/screenshot-current-tweet.ts\")\r\n\r\n```js\r\n// Name: Screenshot Current Tweet\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\nlet url = await getActiveTab()\r\nlet timeout = 5000\r\nlet headless = false\r\n\r\nconst browser = await chromium.launch({\r\n timeout,\r\n headless,\r\n})\r\n\r\nconst context = await browser.newContext({\r\n colorScheme: \"dark\",\r\n})\r\nconst page = await context.newPage()\r\npage.setDefaultTimeout(timeout)\r\n\r\nawait page.goto(url)\r\n\r\nlet screenshotPath = home(\r\n \"Downloads\",\r\n path.parse(url).name + \".png\"\r\n)\r\n\r\ntry {\r\n await page\r\n .locator(\"article[tabindex='-1']\")\r\n .screenshot({ path: screenshotPath })\r\n await revealFile(screenshotPath)\r\n log(`Done`)\r\n} catch (error) {\r\n log(error)\r\n}\r\n\r\nawait browser.close()\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1092","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1088","url":"https://github.com/johnlindquist/kit/discussions/1088","title":"Sleep on Shortcode Example (similar to using an \"alias\")","name":"Sleep on Shortcode Example (similar to using an \"alias\")","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1088","createdAt":"2023-02-01T22:39:21Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXgX","body":"To run the script quickly, from the main prompt type:\r\n\r\ns, then l, then space, then y to confirm.\r\n\r\nSo these four characters:\r\n```\r\nsl y\r\n```\r\n\r\nUsing the `//Shortcode: ` metadata will run the script when you hit the \"spacebar\" after a shortcode/alias.\r\n\r\n\r\n[Open sleep-on-shortcode in Script Kit](https://scriptkit.com/api/new?name=sleep-on-shortcode&url=https://gist.githubusercontent.com/johnlindquist/35e843a00efebb5cceeeb65ea45eb779/raw/e83dd71468ff034c9d5f288387457438d1492c38/sleep-on-shortcode.ts\")\r\n\r\n```js\r\n// Name: Sleep on Shortcode\r\n// Shortcode: sl\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet confirm = await arg({\r\n placeholder: `Sleep system?`,\r\n// Script Kit parses hints and assigns single key shortcuts to single letters inside of []\r\n hint: `[y]/[n]`,\r\n})\r\n\r\nif (confirm === \"y\") {\r\n sleep()\r\n}\r\n\r\n```\r\n\r\n\r\n> Sidenote: From the main menu, you can also type `-` to bring up system commands.","value":"https://github.com/johnlindquist/kit/discussions/1088","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1087","url":"https://github.com/johnlindquist/kit/discussions/1087","title":"Color Picker with saved themes","name":"Color Picker with saved themes","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1087","createdAt":"2023-02-01T21:31:53Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXeJ","body":"\r\n[Open theme-color-picker in Script Kit](https://scriptkit.com/api/new?name=theme-color-picker&url=https://gist.githubusercontent.com/Ambushfall/990b39e9515ac138da5d5c4a5b783f47/raw/19af7566bd72f0e69763b729ee0857a3a1c18130/theme-color-picker.js\")\r\n\r\n```js\r\n// Name: Theme Maker\r\n// Description: Test Themes\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n\r\nconst themePath = kenvPath('theme.json');\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\nconst list = [\"foreground\", \"accent\", \"ui\", \"background\"]\r\nconst valueList = [foreground, accent, ui, background, opacity]\r\n\r\n\r\nconst arrayList = list.map((value, index) => {\r\n return { type: \"color\", label: value, value: valueList[index] }\r\n})\r\n\r\narrayList.push({ type: \"range\", label: \"Opacity\", value: opacity })\r\n\r\nawait fields({\r\n onChange: (input, { value }) => {\r\n const [foreground, accent, ui, background, opacity] = value\r\n theme.foreground = foreground\r\n theme.accent = accent\r\n theme.ui = ui\r\n theme.background = background\r\n theme.opacity = opacity\r\n\r\n setTheme(theme);\r\n writeJson(themePath, theme)\r\n },\r\n fields: arrayList,\r\n})\r\n\r\nlet result = await div(md(`# Success!`))\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1087","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1086","url":"https://github.com/johnlindquist/kit/discussions/1086","title":"Widget Color Picker","name":"Widget Color Picker","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1086","createdAt":"2023-02-01T21:17:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXdp","body":"# Customize your own theme with the color picker\r\n#### Note: Heavily influenced by johnlindquist\r\n\r\n[Open widget-theme in Script Kit](https://scriptkit.com/api/new?name=widget-theme&url=https://gist.githubusercontent.com/Ambushfall/f985c74005580f816a9eaf27852d5902/raw/57c482fec59b1d7ebacde8a2a42010f957af1249/widget-theme.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Name: Widget Theme Picker\r\n// Description: Color Picker HTML\r\n\r\nlet themePath = kenvPath(\"theme.json\")\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\n\r\nlet w = await widget(\r\n `\r\n
`,\r\n {\r\n width: 300,\r\n height: 300,\r\n draggable: false,\r\n }\r\n)\r\n\r\nw.onInput(event => {\r\n setTheme({\r\n [event.dataset.label]: event.value,\r\n })\r\n theme[event.dataset.label] = event.value\r\n writeJson(themePath, theme)\r\n})\r\n\r\nsetIgnoreBlur(true)\r\nawait mainScript()\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1086","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1085","url":"https://github.com/johnlindquist/kit/discussions/1085","title":"Theme Creator Prototype","name":"Theme Creator Prototype","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/1085","createdAt":"2023-02-01T19:09:49Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASXXu","body":"\r\n[Open theme-creator in Script Kit](https://scriptkit.com/api/new?name=theme-creator&url=https://gist.githubusercontent.com/johnlindquist/72c592fb12ecb45cac31735e572f5516/raw/827416e4c3d92d84144fe2e763b4e3ca3ed849e3/theme-creator.ts\")\r\n\r\n```js\r\n// Name: Theme Creator\r\n\r\n// This will create a file at ~/.kenv/theme.txt\r\n// Edit the file, then hit save to update the theme\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet themePath = kenvPath(\"theme.txt\")\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `\r\n--color-primary: 255, 155, 255\r\n--color-secondary: 255, 113, 39\r\n--color-background: 255, 255, 255\r\n `.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nawait edit(themePath)\r\n\r\nlet { watch } = await npm(\"chokidar\")\r\n\r\nsetIgnoreBlur(true)\r\nlet mS = mainScript()\r\n\r\nwatch(themePath).on(\"change\", async () => {\r\n let contents = await readFile(themePath, \"utf-8\")\r\n\r\n let theme = contents.split(\"\\n\").reduce((acc, line) => {\r\n let [k, v] = line.trim().split(\":\")\r\n acc[k.trim()] = v.trim()\r\n return acc\r\n }, {})\r\n\r\n setTheme(theme)\r\n})\r\n\r\nawait mS\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1085","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1083","url":"https://github.com/johnlindquist/kit/discussions/1083","title":"Copy MacOS version to the clipboard","name":"Copy MacOS version to the clipboard","extension":".md","description":"Created by danielo515","resourcePath":"/johnlindquist/kit/discussions/1083","createdAt":"2023-02-01T09:21:49Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASW69","body":"Lots of support tickets asks me this information that is tedious to get\r\n\r\n[Open os-version in Script Kit](https://scriptkit.com/api/new?name=os-version&url=https://gist.githubusercontent.com/danielo515/244ef0a7557ec42218f9e194f8c85a98/raw/3ec404f3ee8600074b57081a3d4700f2431f2b4f/os-version.js\")\r\n\r\n```js\r\n// Name: os version\r\n\r\nimport \"@johnlindquist/kit\"\r\nconst version_info = (await $`sw_vers`).stdout\r\nconst version_lines = version_info.split(\"\\n\");\r\nconst version_number = version_lines[1].replace(/(?:.*?)(\\d+)/,\"$1\");\r\ncopy(`macOS ${version_number}`);\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1083","img":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1082","url":"https://github.com/johnlindquist/kit/discussions/1082","title":"Theme Maker - Test","name":"Theme Maker - Test","extension":".md","description":"Created by Ambushfall","resourcePath":"/johnlindquist/kit/discussions/1082","createdAt":"2023-02-01T05:12:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASWx3","body":"### For all those that would like to test customizing\r\n\r\nNote: This preview will most likely stop working at some point\r\n\r\n\r\n\r\n[Open theme-color-picker in Script Kit](https://scriptkit.com/api/new?name=theme-color-picker&url=https://gist.githubusercontent.com/Ambushfall/6fb945ea689530704ec081c48e2bf382/raw/a1174df6d04fafcb430b9c981a3aec55a56746d4/theme-color-picker.js\")\r\n\r\n```js\r\n// Name: Theme Maker\r\n// Description: Test Themes\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst setArgOptions = (key, val) => {\r\n return {\r\n name: key, type: \"color\", input: val, onInput: async input => {\r\n const scriptObj = {}\r\n scriptObj[key] = input\r\n setScriptTheme(scriptObj)\r\n }\r\n }\r\n}\r\n\r\n\r\nlet foreground = setArgOptions(\"foreground\", \"#ffffff\")\r\nlet accent = await arg(setArgOptions(\"accent\", \"#fbbf24\"))\r\nlet ui = await arg(setArgOptions(\"ui\", \"#343434\"))\r\nlet background = await arg(setArgOptions(\"background\", \"#000000\"));\r\nlet opacity = await arg({\r\n type: \"range\",\r\n name: \"opacity\",\r\n input: \"85\",\r\n onInput: async input => setScriptTheme({opacity:`0.${input}`})\r\n})\r\n\r\n\r\nlet result = await div(md(`# Success!`))\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/1082","img":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/98099231?u=77e036c43b032db40acee8aa70c217e03a74d480&v=4","user":"orhan-erday","author":"Orhan Erday","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1079","url":"https://github.com/johnlindquist/kit/discussions/1079","title":"Clone a repository","name":"Clone a repository","extension":".md","description":"Created by orhan-erday","resourcePath":"/johnlindquist/kit/discussions/1079","createdAt":"2023-01-31T07:53:16Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDg0MTcw","name":"Share","emoji":":smiling_face_with_three_hearts:"},"id":"D_kwDOEu7MBc4ASV62","body":"**Important: Please create `~/GithubProjects` folder**\r\n\r\n[Open with ScriptKIT](https://scriptkit.com/api/new?name=search-starred-repos&url=https://gist.github.com/orhan-erday/6951107bf773dafb3217a41d801c5185)\r\n\r\n```\r\n// Name: Clone A Repository\r\n// Author: Orhan Erday\r\n// Twitter: @orhan_erday\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet repo = await arg(\"Enter a repository, i.e: orhanerday/open-ai\")\r\nlet name = repo.split(\"/\")[1]\r\n// inspect(name)\r\n\r\n// create a folder that called ~/GithubProjects\r\nawait term({\r\n //defaults to home dir\r\n cwd: `~/GithubProjects`,\r\n command: `git clone https://github.com/${repo} && cd ${name} && code .`,\r\n // The footer is optional. All terms continue with the same shortcuts\r\n footer: `ctrl+c or cmd+enter to continue`,\r\n })\r\n```","value":"https://github.com/johnlindquist/kit/discussions/1079","img":"https://avatars.githubusercontent.com/u/98099231?u=77e036c43b032db40acee8aa70c217e03a74d480&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/970","url":"https://github.com/johnlindquist/kit/discussions/970","title":"Script Kit v1.39.17 December Release","name":"Script Kit v1.39.17 December Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/970","createdAt":"2022-12-08T02:29:43Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ARsPf","body":"# Script Kit v1.39.17 December Release\r\n\r\n\r\n\r\n\r\nDownload from https://www.scriptkit.com/\r\n\r\n## Kit.app Re-Design\r\n\r\nScript Kit now has a new look. The theming was completely re-written to help build out customs themes, so we took the opportunity to clean up the main theme.\r\n\r\nYou may also notice subtle differences in app shadows, transparency, etc which are thanks to the latest Electron updates supporting \"Panel\" windows (which we were accomplishing through third-party, less-than-ideal ways before). Speaking of updates...\r\n\r\n## Updated to the Latest Versions\r\n\r\nScript Kit is now on the latest Electron 22, node 16.17.1, and many of the internal dependencies how been updated as well. If you're a developer, you know how big of an undertaking this can be (and how refreshing it is when you're done).\r\n\r\nElectron 22 is an exciting release from a dev standpoint and we can't wait to dig into using some of the new features.\r\n\r\n## Alternate Keymap Support\r\n\r\nScript Kit shortcuts will now support your custom/international keymaps. If will also detect if a keymap has changed and remap based on the new configuration\r\n\r\n> Note: If you have a shortcut that accounted for the keymap being \"wrong\", you'll need to update it to the correct version\r\n\r\n## Run a Script From Other Apps with ~/.kit/run.txt\r\n\r\nIf you want to launch Script Kit from the terminal or another app, write the command and arguments to the `~/.kit/run.txt` file. \r\nScript Kit watches for changes and will run the command you write to the file.\r\n\r\nThe following will run the script `browse-scriptkit.js` in your ~/.kenv/scripts dir:\r\n\r\nMac:\r\n```bash\r\necho browse-scriptkit > ~/.kit/run.txt\r\n```\r\n\r\nWindows:\r\n```cmd\r\necho browse-scriptkit > %HOMEPATH%\\.kit\\run.txt\r\n```\r\n\r\n> Note: You can still use ~/.kit/kar, but I wanted to offer an alternative to our Windows friends\r\n\r\n## Watchers Menu\r\n\r\n> 🚨 Note: It's now required to manually start the snippet/clipboard watcher from the menubar icon->Watchers menu.\r\n\r\n Some users reported difficulty/freezing with the keyboard monitoring for snippets due to various other app conflicts, hardware conflicts, etc, so we decided to allow more control over starting/stopping/waking the watcher in case something happens. We worked hard in this release to address these issues, but decided it's still best to give control to the user.\r\n\r\n## Windows setSelectedText() Fixed\r\n\r\nWindows will now properly hide the prompt to be able to paste text to the app behind it\r\n\r\n## Soon... Scripts on GitHub Actions\r\n\r\nMade significant progress towards using Script Kit on GitHub Actions. Need some more time to test and handle edge cases, but we're close!!!","value":"https://github.com/johnlindquist/kit/discussions/970","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/949","url":"https://github.com/johnlindquist/kit/discussions/949","title":"Script Kit v1.36.0 November 2022 Release","name":"Script Kit v1.36.0 November 2022 Release","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/949","createdAt":"2022-11-18T17:39:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AReJw","body":"# Script Kit v1.36.0 November 2022 Release\r\n\r\nDownload from [https://www.scriptkit.com/](https://www.scriptkit.com/)\r\n\r\n\r\n
\r\n \r\n
\r\n\r\n\r\n## Script Markdown\r\n\r\nPlace a multiline comment at the top of your script to display Markdown to the user when previewing the script. For example:\r\n\r\n```js\r\n/*\r\n# Rotate Images\r\n- Get the selected image from Finder\r\n- Creates 3 new versions rotated at 90, 180, and 270\r\n*/\r\n```\r\n\r\n## Script `Enter` Metadata\r\n\r\nAdding `Enter` metadata will now change the text displayed by the \"Run\" button in the script preview. For example:\r\n\r\n```js\r\n// Enter: Rotate Images\r\n```\r\n\r\n## Themes (Pro)\r\n\r\nFrom the \"Kit\" tab, you can now select a theme for the Script Kit UI. There are a variety of themes to choose from and in upcoming releases, you'll be able to create your personalized themes that you can share.\r\n\r\n## Log Window (Pro)\r\n\r\nRun a script with `alt+enter` to open the log window. This window will display the output of your script from and commands you run or any console logs.\r\n\r\n## Debugger (Pro)\r\n\r\nRun a script with `ctrl+enter` to open the debugger. The debugger will automatically pause on any `debugger` statements and allow you to step through your script and inspect/modify variables.\r\n\r\n## Account\r\n\r\nYou can now sign in to GitHub to unlock more features. The first feature is `createGist` (which is used behind the scenes in sharing scripts already) which is now exposed to users:\r\n\r\n```js\r\nlet {url} = await createGist(\"My content\")\r\n```\r\n\r\nIn the future, this account will be used for:\r\n- syncing your scripts with a GitHub repo\r\n- connecting to GitHub repos to run GitHub Actions\r\n- displaying stats about your scripts\r\n- pro plan/team plans\r\n- and much more...\r\n\r\n\r\n## Widgets Dynamic Lists\r\n\r\nWidgets can now use dynamic lists and get data from an item selected. Please check out this example and watch the youtube video for more details.\r\n\r\nhttps://github.com/johnlindquist/kit/discussions/948\r\n\r\n## Snippet Keyboard Layouts\r\n\r\nSome users reported that Snippets were not working on non-standard keyboard layouts. The snippet engine has been updated to detect your current system keyboard layout and adjust accordingly.\r\n\r\n## Focus Window\r\n> Requires \"Security & Privacy\" > \"Accessibility\" > \"Screen Recording\" permission to be enabled to work so it can get the window title names.\r\n\r\nHit colon (:) from the main menu to open the focus window script. This lists all of the windows open and allows you to select which window to bring into focus.\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/949","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/925","url":"https://github.com/johnlindquist/kit/discussions/925","title":"October 2022 Release (v1.32.2) - Windows Preview 🤩, Debugger 🐞, and Repairs 🛠️","name":"October 2022 Release (v1.32.2) - Windows Preview 🤩, Debugger 🐞, and Repairs 🛠️","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/925","createdAt":"2022-10-27T16:08:15Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ARNRI","body":"# Script Kit 1.32.2 Released 🎉\r\n\r\nDownload here: https://scriptkit.com\r\n\r\n## Windows Preview 🤩\r\n\r\nWe are excited to announce the first Windows preview of Script Kit! This build is very close to feature-parity with the OSX version, so you can expect the vast majority of scripts you run on Mac to also run on Windows.\r\n\r\n> Note: We haven't purchased the Windows code signing certificate yet, so Windows will warn you that it can't verify Script Kit when you begin the install process. Also, this causes that Windows updates will need to be installed manually. We plan on setting up the Windows code-signing certificate by the end of the year to fix these issues then we'll remove the \"Preview\" label 😊\r\n\r\nSnippets, Watchers, Schedule, etc, etc, etc all work. If an api is not supported on Windows (mostly the functions that get information about the desktop) then it will display a \"Not supported on Windows\" message.\r\n\r\nThe \"Browse\" (`/` from the main menu) and \"File Search\" (`.` from the main menu) both required a ton of work, but they're working as well. We'll get the App Launcher sorted out in the next release.\r\n\r\n## Debugger\r\n\r\nYou can now debug your scripts by simply pressing `cmd+enter` from the main menu (`ctrl+enter` on Windows). This will open a built-in inspector that will allow you to step through your script, set breakpoints, and inspect variables.\r\n\r\nUse the `debugger` keyword to set a breakpoint in your script. The inspector will pause execution when it hits the breakpoint and you can mess around with the variables and step through the script to your heart's content. You can even invoke functions such as `setDescription()` and treat the inspector like a REPL.\r\n\r\n## Repair\r\n\r\nThe menubar now includes a `Debug` menu. From `Debug->Force Repair Kit SDK` you can force the Kit SDK to be reinstalled. This is useful if you're having issues with the Kit SDK due to npm acting up or if an update failed.\r\n\r\n## `await cutText()`\r\n\r\nThe `cutText()` function will cut the latest typed word and bring it into your script.\r\n\r\n```js\r\nlet word = await cutText()\r\n\r\n// Send word to an API, wrap the word in markdown, etc, etc\r\n```\r\n\r\n## Other Fixes\r\n\r\n* The TypeScript esbuild compiling should be faster and more stable\r\n* New install splash now shows the npm progress status\r\n* App Launcher fixes (`;` from the main menu)\r\n* Google Fixes (`~` from the main menu)","value":"https://github.com/johnlindquist/kit/discussions/925","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/901","url":"https://github.com/johnlindquist/kit/discussions/901","title":"September 2022 Release (version 1.30.8) - Postfix Snippets, UI Shortcuts, VS Code Extension, and Much, much more!","name":"September 2022 Release (version 1.30.8) - Postfix Snippets, UI Shortcuts, VS Code Extension, and Much, much more!","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/901","createdAt":"2022-09-29T08:49:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AQ537","body":"# Script Kit 1.30.8 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\n\r\nThis is our first release after a couple of busy summer months, and it's slam-packed full of new features and UX!\r\n\r\n## Postfix Snippets\r\n\r\nPostfix snippets are snippets that are triggered by typing a postfix after a \"variable\" of text. The variable is represented by a `*` at the beginning of snippet. In the `*html,,` example below, typing `divhtml,,` would treat `div` as the variable and render out `
Hello world
`.\r\n\r\n### Postfix Snippet Hello World\r\n\r\n```js\r\n// Name: Example Postfix Snippet\r\n// Snippet: *html,,\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"I'm the '*' content\")\r\n\r\nsetSelectedText(`<${value}>Hello world${value}>`)\r\n```\r\n\r\n### Postfix Snippet Query API\r\n\r\nThe snippet can also take the content of the `*` and post it to an API for more complex scripts.\r\n\r\nIn this example, the content is used to query google and create a markdown link from the word you typed.\r\n\r\n```js\r\n// Name: Markdown Link from Google Snippet\r\n// Snippet: *,.\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet google = await npm(\"googlethis\")\r\n\r\nconst options = {\r\n page: 0, \r\n safe: false,\r\n additional_params: { \r\n hl: 'en' \r\n }\r\n}\r\n\r\nlet value = await arg(\"I'm the asterisk content\")\r\nlet response = await google.search(value, options);\r\n\r\nlet url = response.results[0].url\r\n\r\nsetSelectedText(`[${value}](${url})`)\r\n```\r\n\r\n\r\n## `template()`\r\n\r\nThe `template` prompt will present the editor populated by your template. You can then tab through each variable in your template and edit it. \r\n\r\nTemplates pair really, _really_ nicely with Snippets!\r\n\r\n### Template Hello World\r\n\r\n```js\r\nlet text = await template(`Hello $1!`)\r\n```\r\n\r\n### Standard Usage\r\n\r\n```js\r\nlet text = await template(`\r\nDear \\${1:name},\r\n\r\nPlease meet me at \\${2:address}\r\n\r\n Sincerely, John`)\r\n```\r\n\r\n\r\n## `await docs()`\r\n\r\n`docs()` takes the file path of a markdown file and displays it as a list of choices.\r\n\r\nEach h2 is displayed as a choice while the content of the h2 is displayed in the preview.\r\n\r\nSelected the choice will return current h2.\r\n\r\n### Submitting a `docs()` value\r\n\r\nIf you'd rather submit a value instead of the h2, then use an HTML comment to specify the value under the h2's content:\r\n\r\n```md\r\n## I'm the Choice Header\r\n\r\n```\r\n\r\n### `docs()` Example\r\n\r\n```js\r\n// Name: Example Docs\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await download(`https://raw.githubusercontent.com/BuilderIO/qwik/main/README.md`)\r\n\r\nlet filePath = tmpPath(\"README.md\")\r\nawait writeFile(filePath, buffer)\r\n\r\n\r\nlet selectedDoc = await docs(filePath)\r\n\r\ndev({selectedDoc})\r\n```\r\n\r\n## UI Shortcuts in the \"Action Bar\"\r\n\r\nThe September 2022 release adds a new \"Action Bar\" at the bottom of the UI which supports custom shortcuts.\r\n\r\nA shortcut has a `name`, `key`, `onPress` and `bar` property. When you press the shortcut, it will trigger the `onPress` function. You can also click on the shortcut to trigger the `onPress` function.\r\n\r\n```js\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n```\r\n\r\n\r\nAdd each shortcuts to a `shortcuts` array which is passed to the prompt config (most commonly, the first argument of `arg()`).\r\n\r\n### UI Shortcuts Example\r\n\r\n```js\r\n// Name: Shortcuts Example\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet clearInput = {\r\n name: \"Clear\",\r\n key: \"cmd+u\", \r\n onPress: async () => { \r\n setInput(\"\")\r\n },\r\n // optional, but will only display when set\r\n bar: \"right\",\r\n}\r\n\r\nlet reloadChoices = {\r\n name: \"Reload\",\r\n key: \"cmd+l\",\r\n bar: \"right\",\r\n onPress: async () => { \r\n setChoices([\"one\",\"two\",\"three\"])\r\n }\r\n} \r\n\r\nlet submitInputNotChoice = {\r\n name: \"Submit Input\",\r\n key: \"cmd+i\",\r\n bar: \"right\",\r\n onPress: async (input) => { \r\n submit(input)\r\n }\r\n} \r\n\r\nlet runAppLauncher = {\r\n name: \"Launch App\",\r\n key: \"cmd+a\",\r\n bar: \"left\",\r\n onPress: async () => { \r\n await run(kitPath(\"main\", \"app-launcher.js\"))\r\n }\r\n}\r\n\r\nlet result = await arg({\r\n placeholder: \"Shortcut demo\",\r\n shortcuts: [ \r\n clearInput,\r\n reloadChoices,\r\n submitInputNotChoice,\r\n runAppLauncher\r\n ]\r\n}, [\"apple\", \"banana\", \"cherry\"])\r\n\r\nawait div(md(`## ${result}`))\r\n```\r\n\r\n## Recent Script Moved to Top\r\n\r\nThe most recently run script is now moved to the top of the list so that the next time you open the main prompt, you can quickly run it again.\r\n\r\n\r\n## VS Code Extension\r\n\r\nWe now have a VS Code extension which allows you to run scripts directly from VS Code:\r\n\r\nhttps://marketplace.visualstudio.com/items?itemName=johnlindquist.kit-extension\r\n\r\nMore features are coming soon!\r\n\r\n## Menubar Updates\r\n\r\nThe menubar menu now has a \"Dev Tools\" menu which allows you to perform some common commands that might not be obvious from the main UI.\r\n\r\nThe menu also lists all of the running processes so that you can easily terminate them without having to hunt around from process ids.\r\n\r\n## cmd+tab to Widgets and `dev`\r\n\r\nWhen the main prompt is open, a widget is open, or a `dev` window is open, the Script Kit icon will be added to the doc to allow you to cmd+tab back to widgets/editor/dev/etc. Since Script Kit is a combination of a temporary prompt (like Alfred) but also can host long-running widgets, we had to work through various scenarios of when cmd+tab can be available. Hopefully we've landed on a solution that works for everyone.\r\n\r\n## Main Menu `API` and `Guide`\r\n\r\nThe Main Menu of Script Kit now hosts `API` and `Guide` tabs which allow you to easily copy code snippets or create new scripts from the examples. They're also both easy to update, so you can expect more samples and explanations to play with in the future!\r\n\r\n## `await emoji()`\r\n\r\nA brand new emoji picker\r\n\r\n```js\r\nlet e = await emoji()\r\nsetSelectedText(e.emoji)\r\n```\r\n\r\n## `await fields()`\r\n\r\n```js\r\nlet [first, last] = await fields([\"First name\", \"Last name\"])\r\n\r\ndev({\r\n first,\r\n last\r\n})\r\n```\r\n\r\n## `beep()`\r\n\r\nAnd the most exciting announcement of all, `beep()` is now available!\r\n\r\n`beep()` plays a beep sound.\r\n\r\n### `beep()` Example\r\n\r\n```js\r\nbeep()\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/901","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/787","url":"https://github.com/johnlindquist/kit/discussions/787","title":"June 2022 Release (version 1.19.3) - Menu, Native Keyboard, ignoreBlur","name":"June 2022 Release (version 1.19.3) - Menu, Native Keyboard, ignoreBlur","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/787","createdAt":"2022-06-21T16:44:14Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AP4Se","body":"# Script Kit 1.19.3 Released 🎉\r\n\r\nDownload here: https://www.scriptkit.com/\r\nDirect downloads:\r\n* Intel: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3.dmg\r\n* m1: https://github.com/johnlindquist/kitapp/releases/download/v1.19.3/Kit-1.19.3-arm64.dmg\r\n\r\n## Features\r\n\r\n* Moved all notifications to menu colored “dots” (red, orange, green)\r\n * In the future, the user will be able to set these notifications as well\r\n* Completely re-vamped menu for common options\r\n* Implemented an “auth helpers” when user does actions that require “accessibility permissions” (Snippets, clipboard history, setSelectedText, etc)\r\n* `ignoreBlur` allows window to go behind other windows and stay open\r\n* `node` is now stored in `~/.knode` (instead of ~/.kit/node) to allow npx to work in the terminal\r\n* `node` version set to v16.14.2. Version is now synced with Kit.app which resolves conflicts with native packages in scripts\r\n* Keyboard actions (copy/paste/type) have moved from applescript to native code. Snippets, setSelectedText, etc should now feel as “instant” as possible.\r\n* You can now `await hide()` for when you need to make sure the prompt is hidden before continuing the script. This was necessary since the new keyboard actions were so fast.\r\n* Moved the script sharing auth flow to a widget\r\n* Internal: Can now set the state of Kit.app from a script to help with debugging\r\n\r\n\r\n## Fixes\r\n\r\n* App launcher failed to parse malformed App plist icons\r\n* Editor whitespace collapsing on HiDPI screens\r\n* Touchbar key while prompt open would cause crash\r\n* Bin files sometimes didn’t regenerate properly when re-launching the app\r\n* Updater issues\r\n* Background UI not updating when user manually terminates process\r\n* Performance: Moved the file watcher to a spawned process that sends events to the App\r\n\r\n## More to come...\r\n\r\nFinally moved into the new house and settled in. Personal life was too hectic to stream much or do release notes on past couple releases. Expect more streaming, sharing scripts, promotion, and news from me. Cheers! 🥂\r\n\r\n_Here's a preview of the new menu and an example of a dot notification_\r\n\r\n\r\n![The new dot notifications and re-vamped menu](https://cdn.discordapp.com/attachments/963905444823318578/988844882665803926/unknown.png)\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/787","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/745","url":"https://github.com/johnlindquist/kit/discussions/745","title":"March 2022 Release (version 1.7.3) - Widgets! Built-in Terminal, Menu, and Built-in Editor Types","name":"March 2022 Release (version 1.7.3) - Widgets! Built-in Terminal, Menu, and Built-in Editor Types","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/745","createdAt":"2022-03-01T19:45:06Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AO6W1","body":"# March 2022 Release (version 1.7.3)\r\n\r\nScript Kit should auto-update or you can grab the downloads here: https://www.scriptkit.com/\r\n\r\n## Widgets - `await widget()`\r\n\r\nA widget is a detached UI window that can control and listen to a script.\r\n\r\n\r\n\r\n[Open widget-hello-world in Script Kit](https://scriptkit.com/api/new?name=widget-hello-world&url=https://gist.githubusercontent.com/johnlindquist/ca174899643e86f416d301d9599bb4e8/raw/55d334c6dc412c0346a750348d8c0ffa2b8650ba/widget-hello-world.ts\")\r\n\r\n```js\r\n// Name: Widget Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet message = await arg(\"Hello what?\")\r\nawait widget(`
Hello ${message}
`)\r\n\r\n```\r\n\r\n### Widget Events\r\n\r\n\r\n[Open widget-events in Script Kit](https://scriptkit.com/api/new?name=widget-events&url=https://gist.githubusercontent.com/johnlindquist/1ce2972fdeed0773450f4dba3f3f2c00/raw/6834ccde194ea471f403df9366a7ac283cb853bb/widget-events.ts\")\r\n\r\n```js\r\n// Name: Widget Events\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = \"\"\r\nlet count = 0\r\n\r\nlet w = await widget(`\r\n
\r\n`)\r\n})\r\n\r\nw.onInput((event) => {\r\n text = event.value\r\n})\r\n\r\nw.onMoved(({ x, y}) => {\r\n // e.g., save position\r\n})\r\n\r\nw.onResized(({ width, height }) => {\r\n // e.g., save size\r\n})\r\n```\r\n\r\n## Closing a Widget\r\nThere are 3 ways to close a widget:\r\n1. Hit \"escape\" with the widget focused\r\n2. End the process of the widget. Hit cmd+p with the main menu focused to see the running processes or `exit()` anywhere in the script.\r\n3. Use a `ttl` (time to live) in the options when creating a widget\r\n\r\n## \"Always on Top\" and Locking the Widget\r\n\r\n[Open widget-always-on-top in Script Kit](https://scriptkit.com/api/new?name=widget-always-on-top&url=https://gist.githubusercontent.com/johnlindquist/bfd8ec67d9632867b0faf4e808381948/raw/90f766f21af8c88760409215e569baef9d8f0238/widget-always-on-top.ts\")\r\n\r\n```js\r\n// Name: Widget Always on Top\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait widget(`
🇺🇦
`, {\r\n alwaysOnTop: true,\r\n transparent: true\r\n})\r\n\r\n```\r\n\r\nWith a widget focused, press cmd+l to \"Lock\" the widget. This will disable any possible mouse interactions (including moving, resizing, etc) and allow you to click through the widget to any windows underneath.\r\n\r\nTo \"Unlock\":\r\n1. three-fingered swipe up on OSX\r\n2. focus the widget\r\n3. hit cmd+l\r\n\r\nYou can now hit move, escape, etc the widget.\r\n\r\n## Built-in Terminal - `await term()`\r\n\r\n`term` is Script Kit's built-in terminal.\r\n\r\n### From the Main Menu\r\n\r\nType > into the main menu to open `term`\r\n\r\n### From a Script\r\n\r\nUse the `await term()` API to switch to the terminal.\r\n\r\n[Open term-hello-world in Script Kit](https://scriptkit.com/api/new?name=term-hello-world&url=https://gist.githubusercontent.com/johnlindquist/10420bab68da357b572c1e703c2c5a43/raw/a287e443f1e57d4541f50feba28b86e1702ff515/term-hello-world.ts\")\r\n\r\n```js\r\n// Name: Term Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait term(`echo 'Hello World!'`)\r\n```\r\n\r\n> Note: If you want spawn a new Mac terminal, use `await terminal()`\r\n\r\n### Pass Terminal Output to Script\r\n\r\nIf you end the terminal with cmd+enter, the script will continue and you can grab the latest text output from the terminal.\r\n\r\n> 🐞: ctrl+any key will also end the terminal. This is a bug (it was only meant to be ctrl+c) which I'll fix soon. I'm also open to ideas for other shortcuts to \"end\" a terminal that aren't taken by vim/emacs/etc, because I know I'll be missing some.\r\n> 🐞: `term` doesn't grab keyboard focus when opening. I'll get that fixed ASAP!\r\n\r\n[Open term-returns in Script Kit](https://scriptkit.com/api/new?name=term-returns&url=https://gist.githubusercontent.com/johnlindquist/935445caef26d1c13f195533569cd0cc/raw/ca921b09480a3d7b5bf63e7a777011199e642fb9/term-returns.ts\")\r\n\r\n```js\r\n// Name: Term Returns\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet text = await term(`ls ~/.kit`)\r\nawait editor(text)\r\n```\r\n\r\n## Menubar - `menu()`\r\n\r\n`menu` allows you to customize the menubar for Script Kit.\r\n\r\n\r\n\r\n[Open menu-hello-world in Script Kit](https://scriptkit.com/api/new?name=menu-hello-world&url=https://gist.githubusercontent.com/johnlindquist/6aeb6a3f916bfc40e9acd6b9d4388b34/raw/21d8c70da05f08e454d81cb0ccebbbbc82b42f7d/menu-hello-world.ts\")\r\n\r\n```js\r\n// Name: Menu Hello World\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nmenu(`Hello 🌎`)\r\n```\r\n\r\n### Menu with Scripts\r\n\r\nThe second arg of menu can be an array of scripts you wish to present in a drop-down menu. This way, on left-click, you'll get a list of scripts to pick from from the menubar rather than opening the main Script Kit UI.\r\n\r\n[Open menu-with-scripts in Script Kit](https://scriptkit.com/api/new?name=menu-with-scripts&url=https://gist.githubusercontent.com/johnlindquist/0e07e0a8bd4926be6d843ce49fbb4474/raw/4a111ceeae7725525fdcf8bf546105698a4ac4c9/menu-with-scripts.ts\")\r\n\r\n```js\r\n// Name: Menu with Scripts\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n// An empty string means \"Use default Script Kit icon\"\r\nmenu(``, [\r\n \"app-launcher\"\r\n])\r\n```\r\n\r\n## Built-in Editor Types\r\n\r\nScript Kit's built-in editor now loads all of Script Kit's types! This was a huge undertaking that everyone just expects to work. You all know how that feels 😇\r\n\r\n> 🐞: Please let me know if you see any missing. I noticed that I missed the types for `terminal` and `iterm` when putting this post together 🤦♂️.\r\n\r\n## March Plans\r\n\r\nI'm dedicating March to DOCUMENTATION!!! (and bug-fixes)... I have _a lot_ of script requests to follow-up on and work around the newsletter and other non-app stuff. I'm also moving this month, so y'all know how stressful that can be. So expect the April build to be extremely light feature-wise, but I will be set up in the new house ready to much more live-streaming and communication. Can't wait to share more! 🙂\r\n\r\n## Questions?\r\n\r\nI'm happy to help with any questions you may have!","value":"https://github.com/johnlindquist/kit/discussions/745","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/705","url":"https://github.com/johnlindquist/kit/discussions/705","title":"February 2021 (version 1.6.9) - Built-in Editor, New Main Menu Features, `await path()`, Event handlers, Beta pro features, Terminate Process","name":"February 2021 (version 1.6.9) - Built-in Editor, New Main Menu Features, `await path()`, Event handlers, Beta pro features, Terminate Process","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/705","createdAt":"2022-01-30T00:36:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOqIL","body":"Been a _busy_ month of major Script Kit features!\r\n\r\n## Built-in Editor\r\n\r\nScript Kit's number one goal is to make writing time-saving scripts easier. So now Script KIt comes with a pre-configured editor, complete with autocompletion and error checking:\r\n\r\nhttps://user-images.githubusercontent.com/36073/152349718-9f9af13f-4cbb-4444-810a-2f1281938106.mp4\r\n\r\nIf you're already using vs code, you can switch to the \"Kit\" editor in the `Kit` tab -> `Change Editor`.\r\n\r\n## Main Menu Shortcuts and `/`, `~`, and `>`\r\n\r\nThe Script Kit main menu will continue to grow in features. \r\n\r\n### Shortcuts\r\n\r\n* `cmd+f` - Does a `grep` search through all of your scripts\r\n* `cmd+p` - Launches a `Processes` menu for currently running scripts\r\n\r\n### Path Mode\r\nType the following characters to change the mode of the main menu:\r\n* ~ Switches to a path selector mode in your home directory\r\n* / Switches to a path selector mode in your root directory\r\n\r\nNavigate with right/left or tab/shift+tab then select with return. Here's an example of typing `~`\r\n\r\nhttps://user-images.githubusercontent.com/36073/152386816-8be054d6-047c-416f-ae2b-dce1723d222c.mp4\r\n\r\n### Command Mode\r\n\r\n* > Switches to a command mode to execute a command\r\n\r\n\r\nhttps://user-images.githubusercontent.com/36073/152387376-b2e8b71a-4980-4d23-8c98-4f56f3ce1fdd.mp4\r\n\r\n\r\n### Future Work\r\nIn the March release, planning on these:\r\n\r\n* , List system preferences\r\n* . App launcher\r\n* ; MEGA MENU WITH EVERYTHING 🤭\r\n\r\n## `await path()`\r\n\r\nYou can now prompt to select a path. This UI works exactly like \"path mode\" above.\r\n\r\n```js\r\nlet selectedPath = await path()\r\ncd(selectedPath)\r\n\r\nawait exec(`git pull`) // this will now operate based on the selectedPath\r\n```\r\n\r\n## Event Handlers\r\n\r\nWhen building the `path` prompt, I realized it just wasn't possible to do in a script. So I put in the effort to expose the event handlers from the app into the prompt. So even though `path` behaves very differently, it's still an `arg` with customized handlers. You can override many of the handlers yourself for customized prompts:\r\n\r\nFor example, you can override the default behavior of `Escape` terminating your current script:\r\n\r\n```js\r\n// Submit the current input when you hit escape\r\nawait arg({\r\n onEscape: (input)=> {\r\n submit(input)\r\n }\r\n})\r\n```\r\n\r\nOverriding handlers is definitely considered \"advanced\", so I'm happy to answer any questions!\r\n\r\nHere's a list of all the new `arg` config properties:\r\n```js\r\nexport interface ChannelHandler {\r\n (input: string, state: AppState): void | Promise\r\n}\r\n\r\nexport interface PromptConfig\r\n onNoChoices?: ChannelHandler\r\n onChoices?: ChannelHandler\r\n onEscape?: ChannelHandler\r\n onAbandon?: ChannelHandler\r\n onBack?: ChannelHandler\r\n onForward?: ChannelHandler\r\n onUp?: ChannelHandler\r\n onDown?: ChannelHandler\r\n onLeft?: ChannelHandler\r\n onRight?: ChannelHandler\r\n onTab?: ChannelHandler\r\n onInput?: ChannelHandler\r\n onBlur?: ChannelHandler\r\n onChoiceFocus?: ChannelHandler\r\n\r\n debounceInput?: number\r\n debounceChoiceFocus?: number\r\n\r\n onInputSubmit?: {\r\n [key: string]: any\r\n }\r\n onShortcutSubmit?: {\r\n [key: string]: any\r\n }\r\n}\r\n```\r\n\r\n## onInputSubmit, onShortcutSubmit\r\n\r\nIf you want to create \"shortcuts\" to submit specific values, can use the new `onInputSubmit` and `onShortcutSubmit`. These allow you to bind text or shortcuts to submit values. This is exactly how the main menu works:\r\n\r\n![CleanShot 2022-02-03 at 09 47 42](https://user-images.githubusercontent.com/36073/152388672-db242f4e-20e2-4645-a326-a8bbc960f63d.png)\r\n\r\n\r\n## Beta Pro Features: Menubar\r\n\r\nYou can now customize the text of the Script Kit menubar icon to say anything with the `pro.beta.menubar` method. In the future, you'll be able to build out an entire menu, but I thought I'd sneak this feature in for fun in this build:\r\n\r\n[Open menubar-demo in Script Kit](https://scriptkit.com/api/new?name=menubar-demo&url=https://gist.githubusercontent.com/johnlindquist/ef01308eb63715970f26ee1378473194/raw/9ad4219968d9ec1a9747811a71c678ad8e241ec0/menubar-demo.ts\")\r\n\r\n```js\r\n// Name: Menubar Demo\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet value = await arg(\"Set the menubar to:\")\r\npro.beta.menubar(value)\r\n\r\n\r\n```\r\n\r\nhttps://user-images.githubusercontent.com/36073/152394319-d9e071fe-edcd-4cf2-be3e-60d5ba7b01cd.mp4\r\n\r\n\r\n## Terminate Processes\r\n\r\nIf you need to end a script that's running in the background, stuck on an exec command, or whatever reason, open the main menu with the cmd+; shortcut, then press this button (or hit cmd+p. This will open a \"terminate processes\" window where you can end your scripts:\r\n\r\n![CleanShot 2022-02-03 at 10 20 45@2x](https://user-images.githubusercontent.com/36073/152394881-cf612921-1f00-4458-861a-3538053377dd.png)","value":"https://github.com/johnlindquist/kit/discussions/705","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/669","url":"https://github.com/johnlindquist/kit/discussions/669","title":"Script Kit for Linux - Developer Preview","name":"Script Kit for Linux - Developer Preview","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/669","createdAt":"2021-12-24T08:15:12Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOXBA","body":"# Script Kit for Linux - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. Building from the \"Mac\" source\r\n\r\nCurrently, the Linux build builds from the exact same branch as the Mac build. While this works fine, for now, we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out details next year.\r\n\r\n2. The Linux build is missing all the OS-specific tools\r\n\r\nLinux currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n3. I've Only Tested on Ubuntu through a Parallels vm\r\n\r\nObviously will need some more real-world testing.\r\n\r\n## Where to Download\r\n\r\nDownload the AppImage here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1","value":"https://github.com/johnlindquist/kit/discussions/669","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/655","url":"https://github.com/johnlindquist/kit/discussions/655","title":"🥳 Script Kit Launch Day 🎉","name":"🥳 Script Kit Launch Day 🎉","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/655","createdAt":"2021-12-17T18:33:08Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOUTd","body":"# Script Kit is Officially Released! 🎉\r\n\r\nDownload now from https://scriptkit.com\r\n\r\n## Free Script Kit Course on [egghead.io](https://egghead.io)\r\n\r\nTo help you get started, I put together a short course as a feature tour:\r\n\r\nhttps://egghead.io/courses/script-kit-showcase-for-optimizing-your-everyday-workflows-e20ceab4\r\n\r\nIf you've been using Script Kit for a while on the beta, you know it can do much, much more than what's shown in the lessons, but everyone has to start somewhere. Speaking of the beta...\r\n\r\n## Beta Channel Discontinued\r\n\r\nIf you installed the beta, please download from https://scriptkit.com, quit Kit.app, and replace with the new version. This will put you on the “Main” channel. Updates will be ~monthly. The beta channel is discontinued ❗️\r\n\r\nAlso, thank you so, so much for all your feedback and patience with updates over the past year. You’ve really helped make Script Kit what it is today and I’m forever grateful 🙏\r\n\r\n## Windows Developer Preview\r\n\r\nThe details for the Windows build are found here: https://github.com/johnlindquist/kit/discussions/654\r\n\r\n## Plans for 2022\r\n\r\n1. Make the dev setup more contribution-friendly. I would love to accept PRs later next year.\r\n2. Get the Windows build to parity with Mac.\r\n3. Lots of lessons and scripts. I can finally spend more time sharing scripts than working on the app 😎\r\n4. Research into Rust, runtimes, and utilities that can provide any benefit to making our scripts better.\r\n5. Focus on \"export to serverless function\", \"export as github action\", and other ways to maximize the work you put into your scripts.\r\n5. Script Kit Pro. A paid version with additional features not found in the base version. Not ready to talk about it, but it's exciting!\r\n","value":"https://github.com/johnlindquist/kit/discussions/655","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/654","url":"https://github.com/johnlindquist/kit/discussions/654","title":"Script Kit for Windows - Developer Preview","name":"Script Kit for Windows - Developer Preview","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/654","createdAt":"2021-12-17T15:06:29Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOUJx","body":"# Script Kit for Windows - Developer Preview\r\n\r\n## Why \"Developer Preview\"\r\n\r\n1. I haven't bought a certificate to add to the build:\r\n- You'll see many \"untrusted\" warnings when downloading/installing\r\n- Auto-updating will not work\r\n\r\n2. I haven't decided if the Windows repo will be a fork, branch, or main\r\n\r\nCurrently, the Windows build builds from the exact same branch as the Mac build. While this works fine, for now, I'm pretty sure we'll want two separate release cadences and feature sets. This could be done through a branch, but then I'd have to set up a new release server. I'll figure it out the details next year.\r\n\r\n3. The Windows build is missing all the OS-specific tools\r\n\r\nWindows currently doesn't support `getSelectedText`, `getTabs`, and other utils that are written in AppleScript. I'm planning to tackle many of those utils in Rust, which theoretically should allow them to be cross-platform, but that's another task for next year.\r\n\r\n4. I've Only Tested It on Two Laptops\r\n\r\nThe Mac version has been used/tested by many, many people. I have two Windows laptops at home to test it on. It works well, but I don't know how much your mileage will vary.\r\n\r\n## Where to Download\r\n\r\nDownload the installer here:\r\nhttps://github.com/johnlindquist/kitapp/releases/tag/v1.5.1\r\n\r\nAgain, this build will not auto-update. I'll post announcements here when new versions are available and you'll have to download the new version each time until I have the certificate and release servers worked out. Honestly, I'll probably write a \"check for Windows update and download\" script then you can just run that on a `// Schedule: 0 8 * * *` 😉","value":"https://github.com/johnlindquist/kit/discussions/654","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/615","url":"https://github.com/johnlindquist/kit/discussions/615","title":"beta.114 - Info, Settings, Choice Events 🎛","name":"beta.114 - Info, Settings, Choice Events 🎛","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/615","createdAt":"2021-11-22T19:09:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOHN6","body":"# beta.114 - Info, Settings, Choice Events\r\n\r\n## Displaying Temporary Info\r\n\r\nUntil now, `await div()` worked by waiting for the user to hit enter/escape. This still works fine, but if you want to \"timeout\" a `div` to display temporary info without user input, this entire script will run without any user interaction:\r\n\r\n[Install non-blocking-div](https://scriptkit.com/api/new?name=non-blocking-div&url=https://gist.githubusercontent.com/johnlindquist/87e92156251d09a02154f04772f1e9bf/raw/be6dde40a7f5e1f3b8eaa9abf68d8698031cd3de/non-blocking-div.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet classes = `p-5 text-3xl flex justify-center items-center text-center`\r\n\r\ndiv(`Wait 1 second...`, classes)\r\nawait wait(1000)\r\n\r\ndiv(`Just 2 more seconds...`, classes)\r\nawait wait(2000)\r\n\r\ndiv(`Almost there...`, classes)\r\nawait wait(3000)\r\n\r\n```\r\n\r\n## Remember Selection\r\n\r\nI needed to build a settings \"panel\", so I wanted to make a list that could toggle. \r\n\r\n\r\n![CleanShot 2021-11-22 at 12 08 29](https://user-images.githubusercontent.com/36073/142920816-3bf47911-578b-4e2f-9662-10257287fde4.png)\r\n\r\nThe solution was to remember the previous choice by `id`. Any time `arg` is invoked, it will check to see if a choice has an id that matched the previously submitted choice and focus back on it. This enables you to hit enter repeatedly to toggle a choice on and off.\r\n\r\n[Install remember-selection](https://scriptkit.com/api/new?name=remember-selection&url=https://gist.githubusercontent.com/johnlindquist/a86395d809c260d943f9763023f5a6f0/raw/4c1057b8500fcdf34fcd179af52f09cc7dee9ca4/remember-selection.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Off\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"On\",\r\n },\r\n]\r\n\r\nlet argConfig = {\r\n placeholder: \"Toggle items\",\r\n flags: {\r\n end: {\r\n shortcut: \"cmd+enter\",\r\n },\r\n },\r\n}\r\n\r\nwhile (true) {\r\n let item = await arg(argConfig, data)\r\n data.find(i => i.id === item.id).name =\r\n item.name === \"On\" ? \"Off\" : \"On\"\r\n\r\n if (flag.end) break\r\n}\r\n\r\nawait div(JSON.stringify(data), \"p-2 text-sm\")\r\n```\r\n\r\nYou could also use this when making a sequence of selections:\r\n\r\n[Install remember-sequence](https://scriptkit.com/api/new?name=remember-sequence&url=https://gist.githubusercontent.com/johnlindquist/80f9d005e5bff92691125f736199aa2c/raw/4e05c118bc91defe5e2f39cff20eb9862f4c6a2d/remember-sequence.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet data = [\r\n {\r\n id: uuid(),\r\n name: \"One\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Two\",\r\n },\r\n {\r\n id: uuid(),\r\n name: \"Three\",\r\n },\r\n]\r\n\r\nlet selections = []\r\n\r\nlet one = await arg(`First selection`, data)\r\nselections.push(one)\r\n\r\nlet two = await arg(\r\n {\r\n placeholder: `Second selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(two)\r\n\r\nlet three = await arg(\r\n {\r\n placeholder: `Third selection`,\r\n hint: selections.map(s => s.name).join(\", \"),\r\n },\r\n data\r\n)\r\nselections.push(three)\r\n\r\nawait div(\r\n selections.map(s => s.name).join(\", \"),\r\n \"p-2 text-sm\"\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n[Install no-choices-event](https://scriptkit.com/api/new?name=no-choices-event&url=https://gist.githubusercontent.com/johnlindquist/5534589a322bbb384e5bf4dbcbf00864/raw/1a7c2500149db3b8731e900646d568fa7fb5ed74/no-choices-event.js\")\r\n\r\n## Choice Events\r\n\r\n`onNoChoices` and `onChoices` allows Kit.app to tell your script when the user has typed something that filtered out every choice. Most commonly, you'll want to provide a `setHint` (I almost made it a default), but you can add any logic you want.\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait arg(\r\n {\r\n placeholder: `Pick a fruit`,\r\n onChoices: async () => {\r\n setHint(``)\r\n },\r\n onNoChoices: async input => {\r\n setHint(`No choices matched ${input}`)\r\n },\r\n },\r\n [`Apple`, `Orange`, `Banana`]\r\n)\r\n\r\n```\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/615","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/587","url":"https://github.com/johnlindquist/kit/discussions/587","title":"beta.98 - Previews 👀, Docs, devTools, updater improvements","name":"beta.98 - Previews 👀, Docs, devTools, updater improvements","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/587","createdAt":"2021-11-12T17:32:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AOC-z","body":"## Previews\r\n\r\nCreating the previews feature was a huge undertaking, but it really paid off. You can now render html into a side pane by simply providing a `preview` function. A preview can be a simple string all the way to an async function per choice that loads data based on the currently selected choice. For examples, see here #555 \r\n\r\n> You can toggle previews on/off with cmd+p\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507471-db3e4454-f0ef-4e6b-891b-bd4344a40e85.mp4\r\n\r\n## Docs\r\n\r\nAlong with previews comes the built-in docs.\r\n\r\n- Docs are built from the GitHub discussions [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) category\r\n- Each time I post/update a doc, a webhook builds the docs into a json file, Kit.app checks for a new docs.json once a day (or you can manually update them from the `Help->Download Latest Docs`\r\n- You can _click an example to install it!_ 🎉\r\n- I'll keep working on docs and examples. Please ask any questions over in the [docs](https://github.com/johnlindquist/kit/discussions/categories/docs) section if you'd like to see something clarified.\r\n\r\nhttps://user-images.githubusercontent.com/36073/141507953-02d44174-3ac0-43d7-8d92-4319e917d512.mp4\r\n\r\n## Dev Tools\r\n\r\nPass any data into `devTools` to pop open a Dev Tools pane so you can interact with the data. `devTools` will first log out the data, but it's also assigned to an `x` variable you can interact with in the console.\r\n\r\n> `devTools` will be another paid feature once Script Kit 1.0 releases\r\n\r\nhttps://user-images.githubusercontent.com/36073/141508954-df3ea997-a49e-4fdd-bd40-7bff76024a6d.mp4\r\n\r\n## Updater Fixes\r\n\r\nA few users reported a strange behavior with the updater. If you've had any issues with it, please download a fresh copy of Kit.app from https://scriptkit.com and overwrite the old version. There are many more guards around the updating logic to prevent those issues from cropping up again.\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/587","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/488","url":"https://github.com/johnlindquist/kit/discussions/488","title":"Script Kit online on Stackblitz ⚡️","name":"Script Kit online on Stackblitz ⚡️","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/488","createdAt":"2021-10-18T20:06:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4AN3Xv","body":"I spent last week getting Script Kit running \"in browser\" to emulate the terminal experience over on Stackblitz. Here's a quick demo:\r\n\r\nhttps://stackblitz.com/edit/node-rnrhra?file=scripts%2Frepos-to-markdown.js\r\n\r\nThe plan is to use this to host interactive demos for the guide/docs. I'd appreciate if you could play around with it a bit and see if I missed anything.\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/488","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/457","url":"https://github.com/johnlindquist/kit/discussions/457","title":"TypeScript support! 🚀","name":"TypeScript support! 🚀","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/457","createdAt":"2021-09-27T17:25:03Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ANtu1","body":"beta.62 brings with it a long-awaited, much-requested feature: TypeScript support!\r\n\r\n![CleanShot 2021-09-27 at 10 42 38](https://user-images.githubusercontent.com/36073/134951810-31754840-85c3-4ad3-a493-59c757fdda07.png)\r\n\r\n## TypeScript Support 🚀\r\n\r\n### 1. But, how?\r\n\r\nEach time your run a TS script, Script Kit will compile the TS script using `esbuild` to a JS script in a `.scripts` dir (notice the \"dot\"). The compiled JS script is then imported from there. Using `.scripts` as a sibling dir will help avoid any `import`/path issues. You can also write TS \"library\" files in your `~/.kenv/lib` dir and import them into your script just fine.\r\n\r\nIf you're experienced with `esbuild` and curious about the settings, they look like this:\r\n\r\n```js\r\nlet { build } = await import(\"esbuild\")\r\n\r\nawait build({\r\n entryPoints: [scriptPath],\r\n outfile,\r\n bundle: true,\r\n platform: \"node\",\r\n format: \"esm\",\r\n external: [\"@johnlindquist/kit\"],\r\n})\r\n```\r\n\r\nThis also opens the door to exporting/building/bundling scripts and libs as individual shippable tools which I'll investigate more in the future.\r\n\r\n### 2. Can I still run my JS scripts if I switch to TS?\r\n\r\nYes! Both your TS and JS scripts will show up in the UI.\r\n\r\n### 3. Why the `import \"@johnlindquist/kit\"`?\r\n\r\nWhen you create a new TS script, the generated script will start with the line: `import \"@johnlindquist/kit\"`\r\n\r\nThis is mostly to make your editor stop complaining by forcing it to load the type definition files and forcing it to treat the file as an \"es module\" so support \"top-level `await`\". It's not technically required since it's not technically importing anything, but your editor will certainly complain very loudly if you leave it out.\r\n\r\n### 4. Where is the setting stored?\r\n\r\nLook in your `~/.kenv/.env` for `KIT_MODE=ts`.\r\n\r\n## fs-extra's added to global\r\n\r\nThe [fs-extra methods](https://www.npmjs.com/package/fs-extra#methods) are now added on the global space. I found myself using `outputFile`, `write/readJson`, etc too often and found them to be a great addition. The only one missing is `copy` since we're already using that to \"copy to clipboard\". You can bring it in with the normal import/alias process if needed, e.g., `let {copy:fsCopy} = await import(\"fs-extra\")`\r\n\r\n## Sync Path\r\n\r\n![CleanShot 2021-09-27 at 11 10 26](https://user-images.githubusercontent.com/36073/134954703-7c9d779f-268a-4f8b-973a-59ac71eebaf0.png)\r\n\r\nYou may notice running scripts from the Script Kit app that some commands you can run in your terminal might be missing, like \"yarn\", etc.\r\n\r\nRun the following command in your terminal to copy the $PATH var from your terminal to your `~/.kenv/.env`. This will help \"sync\" up which commands are available between your terminal and running scripts from the app.\r\n\r\n```bash\r\n~/.kit/bin/kit sync-path\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/457","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/442","url":"https://github.com/johnlindquist/kit/discussions/442","title":"Scripts in GitHub actions (preview)","name":"Scripts in GitHub actions (preview)","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/442","createdAt":"2021-09-21T19:34:15Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"D_kwDOEu7MBc4ANrYA","body":"## tl;dr Here's an example repo\r\n\r\nThe example script creates a release, downloads an image, and uploads it to the release.\r\n\r\nhttps://github.com/johnlindquist/kit-action-example\r\n\r\n## Template Repo\r\n\r\nThis page has a \"one-click\" clone so you can add/play with your own script.\r\n\r\nhttps://github.com/johnlindquist/kit-action-template\r\n\r\n## What is it?\r\n\r\nUse any of your scripts in a GitHub action. `use` the `kit-action` and point it to a scripts in your `scripts` dir:\r\n\r\n```yml\r\nname: \"example\"\r\non:\r\n workflow_dispatch:\r\n pull_request:\r\n push:\r\n branches:\r\n - main\r\n\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\" # The name of a script in your ./scripts dir\r\n```\r\n\r\n## Add env vars:\r\n\r\nYou most likely add [\"secrets\" to GitHub actions](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-an-environment), so you'll want to pass them to your scripts as environment variables:\r\n\r\n```yml\r\njobs:\r\n example:\r\n runs-on: ubuntu-latest\r\n steps:\r\n - name: Script Kit\r\n uses: johnlindquist/kit-action@main\r\n with:\r\n script: \"example-script\"\r\n env:\r\n REPO_TOKEN: \"${{ secrets.REPO_TOKEN }}\" # load in your script with await env(\"REPO_TOKEN\")\r\n```\r\n\r\n## Works with your existing repos\r\n\r\nFeel free to add this action and a `scripts` dir to your existing repos. It automatically loads in your repo so you can parse `package.json`, compress assets, or whatever it is you're looking to add to your CI.\r\n\r\n## What does \"preview\" mean?\r\n\r\nEverything is working, but it's pointing to the \"main\" branch rather than a tagged version. Once I get some feedback, I'll tag a \"1.0\" version so you can `uses: @johlindquist/kit-action@v1`\r\n\r\n## Please ask for help! 😇\r\n\r\nI'd ❤️ to help you script something for a github action! Please let me know whatever I can do to help.","value":"https://github.com/johnlindquist/kit/discussions/442","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/405","url":"https://github.com/johnlindquist/kit/discussions/405","title":"beta.55 Improved Search, Drag, and Happiness 😊","name":"beta.55 Improved Search, Drag, and Happiness 😊","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/405","createdAt":"2021-08-20T21:58:48Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNTMxOTA2","body":"## Search Improvements\r\n\r\nbeta.55 has a vastly improved search:\r\n\r\nSearch descriptions 🎉\r\n\r\n![CleanShot 2021-08-20 at 13 37 44](https://user-images.githubusercontent.com/36073/130285547-24f111d7-a706-4be2-b0d6-b425afbe6683.png)\r\n\r\nSearch shortcuts\r\n\r\n![CleanShot 2021-08-20 at 13 51 49](https://user-images.githubusercontent.com/36073/130286634-4797c029-c2ed-4071-9e1d-285d6bf1a15f.png)\r\n\r\nSearch by kenv\r\n\r\n![CleanShot 2021-08-20 at 13 51 18](https://user-images.githubusercontent.com/36073/130286567-4fd0a155-43a7-4af5-bd3f-c0a9db9ae8dc.png)\r\n\r\nSear by \"command-name\" (if you can't think of // Menu: name)\r\n\r\n![CleanShot 2021-08-20 at 13 54 45](https://user-images.githubusercontent.com/36073/130286878-62b2a139-b3b7-4c34-904f-40d7b03a7e1c.png)\r\n\r\nSorts by \"score\" (rather than alphabetically)\r\n\r\n## Drag\r\n\r\nChoices can now take a `drag` property. This will make list items \"draggable\" and allow you to drag/drop to copy files from your machine (or even from URLs!) into any app. When using remote URLs, their will be a bit of \"delay\" while the file downloads (depending on the file size) between \"drag start\" and \"drop enabled\", so just be aware. I'll add some sort of download progress indicator sometime in the future, just not high priority 😅\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Heart Eyes (local)\",\r\n drag: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n img: \"/Users/johnlindquist/Downloads/john-hearts@2x.png\",\r\n },\r\n {\r\n name: \"React logo svg (wikipedia)\",\r\n drag: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n img: \"https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg\",\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 26 07](https://user-images.githubusercontent.com/36073/130294979-c45aabe2-6c30-41ad-94b4-64a85a2c34eb.gif)\r\n\r\nYou can use the `drag` object syntax to define a `format` and `data`\r\n\r\n> `text/html`: Renders the HTML payload in contentEditable elements and rich text (WYSIWYG) editors like Google Docs, Microsoft Word, and others.\r\n> `text/plain`: Sets the value of input elements, content of code editors, and the fallback from text/html.\r\n> `text/uri-list`: Navigates to the URL when dropping on the URL bar or browser page. A URL shortcut will be created when dropping on a directory or the desktop.\r\n\r\n```js\r\n// Menu: Drag demo\r\n\r\nawait arg(\r\n {\r\n placeholder: \"Drag something from below\",\r\n ignoreBlur: true,\r\n },\r\n [\r\n {\r\n name: \"Padding 4\",\r\n drag: {\r\n format: \"text/plain\",\r\n data: `className=\"p-4\"`,\r\n },\r\n },\r\n {\r\n name: \"I love code\",\r\n drag: {\r\n format: \"text/html\",\r\n data: `I ❤️ code`,\r\n },\r\n },\r\n ]\r\n)\r\n```\r\n\r\n![CleanShot 2021-08-20 at 15 48 00](https://user-images.githubusercontent.com/36073/130296713-6249d5c2-c01f-42d1-b2c2-ea86d2e4c29b.gif)\r\n\r\n## Happiness\r\n\r\nI'm _very_ happy with the state of Script Kit. When I started almost a year ago, I had no idea I could push the concept of creating/sharing/managing custom scripts so far. I think it looks great, feels speedy, and is flexible enough to handle so, so many scenarios.\r\n\r\nWith everything in place, next week I'm starting on creating lessons, demos, and docs. It's time to show you what Script Kit can really do 😉 \r\n\r\nP.S. - Thanks for all the beta-testing and feedback. It's been tremendously helpful!\r\n","value":"https://github.com/johnlindquist/kit/discussions/405","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/397","url":"https://github.com/johnlindquist/kit/discussions/397","title":"beta.46 Design, ⚐ Flags, div, fixed notify","name":"beta.46 Design, ⚐ Flags, div, fixed notify","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/397","createdAt":"2021-08-13T16:33:27Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNTE2OTE1","body":"## Design/theme\r\n\r\nPut a lot of work into tightening up pixels and made progress towards custom themes:\r\n\r\n![CleanShot 2021-08-13 at 09 35 40](https://user-images.githubusercontent.com/36073/129383567-ae628c68-3c96-463f-a47e-4800186ea7ac.png)\r\n\r\nHere's a silly demo of me playing with theme generation:\r\n\r\nhttps://user-images.githubusercontent.com/36073/129384214-2af744ab-8165-4e3f-825d-42fadbf86aec.mp4\r\n\r\n## Flags ⚐\r\n\r\nAn astute observer would notice that the `Edit` and `Share` tabs are now gone. They've been consolidated into a \"flag menu\".\r\n\r\nWhen you press the `right` key from the main menu of script, the flag menu now opens up. This shows the selected script and gives you some options. It also exposes the keyboard shortcuts associated with those options that you can use to :\r\n\r\n![CleanShot 2021-08-13 at 09 42 52](https://user-images.githubusercontent.com/36073/129384559-bff59ebf-88d9-4b95-b9b5-640ce755fe8f.png)\r\n\r\nI've found I use `cmd+o` and `cmd+n` all the time to tweak scripts of quickly create a new one to play around with.\r\n\r\n### Custom Flags\r\n\r\nYou can pass your own custom flags like so:\r\n\r\n[Install flags-demo](https://scriptkit.com/api/new?name=flags-demo&url=https://gist.githubusercontent.com/johnlindquist/b96c8f8de9c256f909ae0f6ab0adda39/raw/9f049cf454f0766fb278e5ee7a24c6b6776df889/flags-demo.js)\r\n\r\n```js\r\n//Menu: Flags demo\r\n\r\nlet urls = [\r\n \"https://scriptkit.com\",\r\n \"https://egghead.io\",\r\n \"https://johnlindquist.com\",\r\n]\r\n\r\nlet flags = {\r\n open: {\r\n name: \"Open\",\r\n shortcut: \"cmd+o\",\r\n },\r\n copy: {\r\n name: \"Copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n}\r\n\r\nlet url = await arg(\r\n { placeholder: `Press 'right' to see menu`, flags },\r\n urls\r\n)\r\n\r\nif (flag?.open) {\r\n $`open ${url}`\r\n} else if (flag?.copy) {\r\n copy(url)\r\n} else {\r\n console.log(url)\r\n}\r\n```\r\n\r\nNotice that `flag` is a global while `flags` is an object you pass to `arg`. This is to help keep it consistent with terminal usage:\r\n\r\nFrom the terminal\r\n```bash\r\nflags-demo --open\r\n```\r\n\r\nWill set the global `flag.open` to `true`\r\n\r\n![CleanShot 2021-08-13 at 10 08 30](https://user-images.githubusercontent.com/36073/129388037-3f27a12d-9e44-4402-a51f-bac39eead54d.png)\r\n\r\n\r\nYou could also run this and pass in all the args:\r\n\r\n```bash\r\nflags-demo https://egghead.io --copy\r\n```\r\n\r\nIn the app, you could create a second script to pass flags to the first with. This is required if you need to pass multiple flags since the `arg` helper can only \"submit\" one per `arg`.\r\n\r\n```js\r\nawait run(`flags-demo https://egghead.io --copy`)\r\n```\r\n\r\nI'll put together some more demos soon. There are plenty of existing CLI tools out there using flags heavily, so lots of inspiration to pull from.\r\n\r\n## `await div()`\r\n\r\nThere's a new `div` \"component\". You can pass in arbitrary HTML. This works well with the `md()` helper which generates `html` from markdown.\r\n\r\n[Install div-demo](https://scriptkit.com/api/new?name=div-demo&url=https://gist.githubusercontent.com/johnlindquist/0ad790953f7101d313abfd48182356b0/raw/c70e17649317986707d2ac714c31afe6f7850015/div-demo.js)\r\n\r\n```js\r\n// Menu: Div Demo\r\n\r\n// Hit \"enter\" to continue, escape to exit\r\nawait div(``)\r\n\r\nawait div(\r\n md(\r\n `\r\n # Some header\r\n\r\n ## You guessed it, an h2\r\n\r\n * I\r\n * love\r\n * lists\r\n `\r\n )\r\n)\r\n\r\n```\r\n\r\n## Fixed `notify`\r\n\r\n`notify` is now fixed so that it doesn't open a prompt\r\n\r\nThe most basic usage is:\r\n\r\n```js\r\nnotify(\"Hello world\")\r\n```\r\n\r\n`notify` leverages [https://www.npmjs.com/package/node-notifier](https://www.npmjs.com/package/node-notifier)\r\n\r\nSo the entire API should be available. Here's an example of using the \"type inside a notification\":\r\n\r\n[Install notify-demo](https://scriptkit.com/api/new?name=notify-demo&url=https://gist.githubusercontent.com/johnlindquist/44387dc5b0c170e4146b061162c33532/raw/1bce77fb778a45cf9052a63d02dcab94a9cf7ef0/notify-demo.js)\r\n\r\n```js\r\n// Menu: Notify Demo\r\nlet notifier = notify({\r\n title: \"Notifications\",\r\n message: \"Write a reply?\",\r\n reply: true,\r\n})\r\n\r\nnotifier.on(\"replied\", async (obj, options, metadata) => {\r\n await arg(metadata.activationValue)\r\n})\r\n\r\n```\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/397","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/365","url":"https://github.com/johnlindquist/kit/discussions/365","title":"beta.33 `console.log` component, cmd+o to Open, `className`","name":"beta.33 `console.log` component, cmd+o to Open, `className`","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/365","createdAt":"2021-07-22T22:44:19Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDczNTEw","body":"## `console.log` Component\r\n\r\nThe follow code will create the below prompt (👀 notice the black background logging component):\r\n```js\r\nlet { stdout } = await $`ls ~/projects | grep kit`\r\n\r\nawait arg(`Select a kit dir`, stdout.split(\"\\n\"))\r\n\r\n```\r\n\r\n\r\n```js\r\nconsole.log(chalk`{green.bold The current date is:}`)\r\nconsole.log(new Date().toLocaleDateString())\r\nawait arg()\r\n```\r\n\r\n\r\n\r\nThe log even persists between prompts:\r\n\r\n```js\r\nlet first = await arg(\"First name\")\r\nconsole.log(first)\r\nlet last = await arg(\"Last name\")\r\nconsole.log(`${first} ${last}`)\r\nlet age = await arg(\"Age\")\r\nconsole.log(`${first} ${last} ${age}`)\r\nlet emotion = await arg(\"Emotion\")\r\nconsole.log(`${first} ${last} ${age} ${emotion}`)\r\nawait arg()\r\n```\r\n\r\n\r\nClick the \"edit\" icon to open the full log in your editor:\r\n![CleanShot 2021-07-22 at 16 20 57@2x](https://user-images.githubusercontent.com/36073/126716749-4eda367a-4e55-424f-915a-30207583cd3f.png)\r\n\r\n## cmd+o to Open\r\n\r\nFrom the main menu, hitting `cmd+o` will open:\r\n\r\n1. The currently selected script from the main menu\r\n2. The currently running script\r\n3. Any \"choice\" that provides a \"filePath\" prop:\r\n\r\n```js\r\nawait arg(`cmd+o to open file`, [\r\n {\r\n name: \"Karabiner config\",\r\n filePath: \"~/.dotfiles/karabiner/karabiner.edn\",\r\n },\r\n {\r\n name: \"zshrc\",\r\n filePath: \"~/.zshrc\",\r\n },\r\n])\r\n```\r\n\r\nI've found this really useful when I want to tweak the running script, but I don't want to go back through the process of finding it.\r\n\r\n## Experimental `className`\r\n\r\nYou can pass `className` into the arg options to affect the container for the list items or panel. Most classes from Tailwind should be available. Feel free to play around with it and let me know how it goes 😇:\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n `\r\n
Working on Script Kit today
\r\n `\r\n)\r\n\r\n```\r\n\r\n\r\n\r\n\r\n```js\r\nawait arg(\r\n {\r\n className: \"p-4 bg-black font-mono text-xl text-white\",\r\n },\r\n [\"Eat\", \"more\", \"tacos 🌮\"]\r\n)\r\n```\r\n\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/365","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/353","url":"https://github.com/johnlindquist/kit/discussions/353","title":"beta.29 M1 build, install remote kenvs, polish, upcoming lessons","name":"beta.29 M1 build, install remote kenvs, polish, upcoming lessons","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/353","createdAt":"2021-07-16T18:29:00Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDY0MDk2","body":"I'm starting on lessons/docs on Monday. If you have anything specific you want me to cover, please reply below!\r\n\r\n## M1 Build\r\n If you're on an M1 mac, you can download the new M1 build from https://www.scriptkit.com/\r\n\r\n1. Download https://www.scriptkit.com/\r\n2. Quit Kit. *note - typing `kit quit` or `k q` in the app is the fastest way to quit.\r\n3. Drag/drop to overwrite your previous build\r\n4. Kit should now auto-update from the M1 channel\r\n5. Open Kit\r\n\r\n## Kenv Management\r\nThere are a lot of tools to help manage other kenvs. They're in the `Kit` menu and once you've installed a remote kenv (which is really just a git repo with a scripts dir), then more options show up in the `Edit` menu to move scripts between kenvs, etc. I'll cover this in detail in the docs/lessons\r\n\r\n## Polish\r\nLots of UI work:\r\n* Remembering position - Each script with a `//Shortcut` will remember its last individual prompt position. For example, if you have a script that uses `textarea`, then drag it to the upper right, the next time you launch that script, it will launch in that position.\r\n* `//Image` metadata - Scripts can now have images:\r\n```js\r\n//Image: https://placekitten.com/64\r\n```\r\nor\r\n```js\r\n//Image: logo.png\r\n```\r\nwill load from `~/.kenv/assets/logo.png`\r\n\r\n\r\n* Spinner - added a spinner for when you submit a prompt and the process needs to do some work before opening the next prompt\r\n\r\n![CleanShot 2021-07-16 at 12 22 58](https://user-images.githubusercontent.com/36073/125992326-7b6f0034-00e8-41df-9ca7-f0e33becf0b2.gif)\r\n\r\n\r\n* Resizing - *Lots* of work on getting window resizing behavior consistent between different UIs. This was a huge pain, but you'll probably never appreciate it 😅\r\n* Lots more - many more small things\r\n\r\n## Lessons!\r\n\r\nI'm starting to work on lessons next week and getting back into streaming schedule. I would ♥️ to hear any specific questions or lessons you would like to see to help you remove some friction from your day. I'll be posting the lessons over on [egghead.io](egghead.io) for your viewing pleasure. Please ask questions in the replies!\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/353","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/330","url":"https://github.com/johnlindquist/kit/discussions/330","title":"Beta.20 MOAR SPEED! ⚡️","name":"Beta.20 MOAR SPEED! ⚡️","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/330","createdAt":"2021-06-26T17:03:40Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDMxOTgz","body":"## Process Pools and Virtualized Lists\r\n\r\nhttps://user-images.githubusercontent.com/36073/123519955-7fdd8200-d66b-11eb-8167-09b9daed1c9f.mp4\r\n\r\n\r\n## Experimental `textarea`\r\n\r\nFeel free to play around with the `textarea` for multiline input.\r\n\r\n```js\r\nlet value = await textarea()\r\n```\r\n\r\nThe API of textarea will change (it currently just sets the placeholder), but it will always return the string value of the textarea, so there won't be any breaking changes if you just keep the default behavior. `cmd+s` submits. `cmd+w` cancels.\r\n\r\n## Experimental `editor` (this will become a _paid_ 💵 feature later this year)\r\n\r\nAs an upgrade to `textarea`, `await editor()` will give you a full editor experience. Same as the textarea, the API will also change, but will always return a string of the content.\r\n\r\n\r\n```js\r\n// Defaults to markdown\r\nlet value = await editor()\r\n```\r\n\r\n> ⚠️ API is subject to change!\r\n```js\r\nlet value = await editor(\"markdown\", `\r\n## Preloaded content\r\n\r\n* nice\r\n`)\r\n```\r\n\r\n```js\r\nlet value = await editor(\"javascript\", `\r\nconsole.log(\"Support other languages\")\r\n`)\r\n```\r\n\r\n### A note on paid features\r\n\r\nEverything you've used so far in the Script Kit app will stay free. The core `kit` is open-source MIT. \r\n\r\nThe paid features will be add-ons to the core experience: Themes, Editor, Widgets, Screenshots, Record Audio, and many more fun ideas. These will roll out experimentally in the free version first then move exclusively to the paid version. Expect the paid versions later this year.\r\n","value":"https://github.com/johnlindquist/kit/discussions/330","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/312","url":"https://github.com/johnlindquist/kit/discussions/312","title":"Beta.19 New Features - Gotta go fast! 🏎💨","name":"Beta.19 New Features - Gotta go fast! 🏎💨","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/312","createdAt":"2021-06-07T20:47:28Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zNDAwNjgx","body":"Beta.19 is all about _speed_! I've finally landed on an approach I love to get the prompt moving waaaay faster.\r\n\r\nCouple videos below:\r\n\r\n## Instant Prompts\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084054-6d270a00-c79d-11eb-8b37-96473de7e0e4.mp4\r\n\r\n```js\r\n// Shortcut: option 5\r\n\r\nlet { items } = await db(async () => {\r\n let response = await get(\r\n `https://api.github.com/users/johnlindquist/repos`\r\n )\r\n\r\n return response.data\r\n})\r\n\r\nawait arg(\"Select repo\", items)\r\n\r\n```\r\n\r\n## Instant Tabs\r\n\r\nhttps://user-images.githubusercontent.com/36073/121084134-85972480-c79d-11eb-9e18-94e5a2efa5d1.mp4\r\n\r\n## Instant Main Menu\r\n\r\nThe main menu now also leverages the concepts behind Instant Prompts listed above.\r\n\r\n## Faster in the future\r\n\r\nThese conventions laid the groundwork for caching prompt data, but I still have plenty ideas to speed things, especially around how the app launches the process. I'm looking forward to making this even faster for you!\r\n\r\nI'm also starting the work on an \"Instant Textarea\" because I know popping open a little textarea to take/save notes/ideas is something many people would use. 📝\r\n\r\n\r\n\r\n\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/312","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/305","url":"https://github.com/johnlindquist/kit/discussions/305","title":"How to Get Your Scripts Featured on ScriptKit.com 😎","name":"How to Get Your Scripts Featured on ScriptKit.com 😎","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/305","createdAt":"2021-06-04T20:07:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzk3MTc2","body":"TL;DR\r\n\r\n- Help -> Create kenv\r\n- Git init new kenv, push to github\r\n- Reply, dm, contact me somehow with the repo 😇\r\n\r\nHere's a video walking you through it:\r\n\r\nhttps://user-images.githubusercontent.com/36073/120856653-6732ee00-c53d-11eb-9dfb-04907b036361.mp4\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/305","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/304","url":"https://github.com/johnlindquist/kit/discussions/304","title":"Beta.18 Changes/Features (`db` has a breaking change)","name":"Beta.18 Changes/Features (`db` has a breaking change)","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/304","createdAt":"2021-06-04T18:09:55Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzk3MDY0","body":"## ⚠️Breaking: New `db` helper\r\n\r\n[lowdb](https://github.com/typicode/lowdb) updated to 2.0, so I updated the `db` helper to support it.\r\n\r\n* access/mutate the objects in the db directly. Then `.write()` to save your changes to the file.\r\n* `await db()` and `await myDb.write()`\r\n\r\nExample with a simple object:\r\n```js\r\nlet shoppingListDb = await db(\"shopping-list\", {\r\n list: [\"apples\", \"bananas\"],\r\n})\r\n\r\nlet item = await arg(\"Add to list\")\r\nshoppingListDb.list.push(item)\r\nawait shoppingListDb.write()\r\n\r\nawait arg(\"Shopping list\", shoppingListDb.list)\r\n```\r\n\r\n\r\nYou can also use an `async` function to store the initial data:\r\n```js\r\nlet reposDb = await db(\"repos\", async () => {\r\n let response = await get(\r\n \"https://api.github.com/users/johnlindquist/repos\"\r\n )\r\n\r\n return {\r\n repos: response.data,\r\n }\r\n})\r\n\r\nawait arg(\"Select repo\", reposDb.repos)\r\n```\r\n\r\n## Text Area prompt\r\n\r\n```js\r\nlet text = await textarea()\r\n\r\ninspect(text)\r\n```\r\n![CleanShot 2021-06-04 at 14 25 12](https://user-images.githubusercontent.com/36073/120858988-cf370380-c540-11eb-8b79-5483ec090dd8.gif)\r\n\r\n\r\n## Optional `value`\r\n\r\n`arg` choice objects used to require a `value`. Now if you don't provide a value, it will simply return the entire object:\r\n\r\n```js\r\nlet person = await arg(\"Select\", [\r\n { name: \"John\", location: \"Chair\" },\r\n { name: \"Mindy\", location: \"Couch\" },\r\n])\r\n\r\nawait arg(person.location)\r\n```\r\n\r\n## ⚗️ Experimental \"Multiple kenvs\"\r\n\r\nThere was a _ton_ 🏋️♀️ of internal work over the past couple weeks to get this working. The \"big idea\" is supporting multiple kit environments. For example:\r\n\r\n* private/personal kenv\r\n* shared kenv\r\n* company kenv\r\n* product kenv\r\n\r\n### Future plans\r\nIn an upcoming release: \r\n* you'll be able to \"click to install kenv from repo\" (just like we do with individual scripts)\r\n* update a git-controlled kenv (like a company kenv)\r\n* the main prompt will be able to search for all scripts across kenvs. \r\n* If multiple kenvs exist, creating a new script will ask you which kenv to create it in.\r\n\r\nFor now, you can try adding/creating/switching the help menu. It should all work fine, but will be _waaaay_ cooler in the future 😎\r\n\r\n![CleanShot 2021-06-04 at 11 50 32](https://user-images.githubusercontent.com/36073/120843227-16b29500-c52b-11eb-974c-a81c260b9ae2.png)\r\n\r\n## Improved Error Prompt\r\n\r\nNow when an error occurs, it takes the error data, shuts down the script, then prompts you on what to do. For example, trying to use the old `db` would result in this:\r\n\r\n![CleanShot 2021-06-04 at 12 03 04](https://user-images.githubusercontent.com/36073/120844575-d6541680-c52c-11eb-8d12-c7c3117e132e.png)\r\n\r\n## Improved Tab Switching\r\nSwitching tabs will now cancel the previous tabs' script. Previously, if you quickly switched tabs on the main menu, the \"Hot\" tab results might show up in a different tab because the loaded _after_ the tab switched. The internals around message passing between the script and the app now have a cancellation mechanism so you only get the latest result that matches the prompt/tab. (This was also a ton of internals refactoring work 😅)\r\n\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/304","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/282","url":"https://github.com/johnlindquist/kit/discussions/282","title":"✨NEW FEATURES✨ beta.17","name":"✨NEW FEATURES✨ beta.17","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/282","createdAt":"2021-05-18T20:24:57Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzcxNjg5","body":"New features are separated into the comments below:\r\n\r\n","value":"https://github.com/johnlindquist/kit/discussions/282","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/245","url":"https://github.com/johnlindquist/kit/discussions/245","title":"✨ NEW ✨ // Background: true","name":"✨ NEW ✨ // Background: true","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/245","createdAt":"2021-05-06T19:36:22Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzU0NDQ0","body":"`beta.12` brings in the ability to start/stop background tasks.\r\n\r\n\r\nUsing `// Background :true` at the top of your script will change the behavior in the main menu:\r\n```js\r\n// Background: true\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n## Auto (like nodemon)\r\n```js\r\n// Background: auto\r\n\r\nsetInterval(() => {}, 1000) //Some long-running process\r\n```\r\n\r\nUsing `auto`, after you start the script, editing will stop/restart the script.\r\n","value":"https://github.com/johnlindquist/kit/discussions/245","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/213","url":"https://github.com/johnlindquist/kit/discussions/213","title":"// Watch: metadata 👀","name":"// Watch: metadata 👀","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/213","createdAt":"2021-04-29T14:31:31Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzQzODM2","body":"Script Kit now supports `// Watch:` metadata\r\n\r\n```js\r\n// Watch: ~/projects/thoughts/**/*.md\r\n\r\nlet { say } = await kit(\"speech\")\r\n\r\nsay(\"journal updated\")\r\n```\r\n\r\n* `// Watch: ` supports any file name, glob, or array (Kit will `JSON.parse` the array).\r\n* Scripts will run on the \"change\" event\r\n* Read more about supported [globbing](https://github.com/micromatch/picomatch#globbing-features)\r\n\r\n> Read about the [other metadata](https://github.com/johnlindquist/kit/discussions/185)\r\n\r\nI would _LOVE_ to hear about scenarios you would use this for or if you run into any issues 🙏","value":"https://github.com/johnlindquist/kit/discussions/213","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/150","url":"https://github.com/johnlindquist/kit/discussions/150","title":"beta.96 - Design, Drop, and Hotkeys! Oh my!","name":"beta.96 - Design, Drop, and Hotkeys! Oh my!","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/150","createdAt":"2021-04-16T20:32:44Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzI1MDcy","body":"\r\nhttps://user-images.githubusercontent.com/36073/115079813-fc5f2200-9ebe-11eb-8e7c-74c8a1d2aee3.mp4\r\n\r\nCan't wait to see what you build! Happy Scripting this weekend! 😇","value":"https://github.com/johnlindquist/kit/discussions/150","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/119","url":"https://github.com/johnlindquist/kit/discussions/119","title":"*New* Choice Preview","name":"*New* Choice Preview","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/119","createdAt":"2021-04-09T21:43:36Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzE0MTM5","body":"\r\nhttps://user-images.githubusercontent.com/36073/114220248-fc907800-9928-11eb-8096-61a5debbdc0d.mp4\r\n\r\n\r\n[Install google-image-search](https://scriptkit.app/api/new?name=google-image-search&url=https://gist.githubusercontent.com/johnlindquist/99756d4e1a54c737dc534c4edb5f6c9d/raw/55c440503a8a653c3ef3dafb9ba1bd567fc0b14a/google-image-search.js)\r\n\r\n```js\r\n// Menu: Google Image Search\r\n// Description: Searches Google Images\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nlet gis = await npm(\"g-i-s\")\r\n\r\nlet selectedImageUrl = await arg(\r\n \"Image search:\",\r\n async input => {\r\n if (input.length < 3) return []\r\n\r\n let searchResults = await new Promise(res => {\r\n gis(input, (_, results) => {\r\n res(results)\r\n })\r\n })\r\n\r\n return searchResults.map(({ url }) => {\r\n return {\r\n name: url.split(\"/\").pop().replace(/\\?.*/g, \"\"),\r\n value: url,\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\ncopy(selectedImageUrl)\r\n\r\n```\r\n\r\n\r\n\r\n[Install giphy-search](https://scriptkit.app/api/new?name=giphy-search&url=https://gist.githubusercontent.com/johnlindquist/dc17a3f07fb41b855e742a0f995cb0ed/raw/109831f9d40a8293b7d8741b44081fddcb024cda/giphy-search.js)\r\n\r\n```js\r\n// Menu: Giphy\r\n// Description: Search giphy. Paste markdown link.\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\nlet download = await npm(\"image-downloader\")\r\nlet queryString = await npm(\"query-string\")\r\nlet { setSelectedText } = await kit(\"text\")\r\n\r\nif (!env.GIPHY_API_KEY) {\r\n show(\r\n `
\r\n
\r\n Grab an API Key from the Giphy dev dashboard:\r\n
`\r\n )\r\n}\r\nlet GIPHY_API_KEY = await env(\"GIPHY_API_KEY\")\r\n\r\nlet search = q =>\r\n `https://api.giphy.com/v1/gifs/search?api_key=${GIPHY_API_KEY}&q=${q}&limit=10&offset=0&rating=g&lang=en`\r\n\r\nlet { input, url } = await arg(\r\n \"Search giphy:\",\r\n async input => {\r\n if (!input) return []\r\n let query = search(input)\r\n let { data } = await get(query)\r\n\r\n return data.data.map(gif => {\r\n return {\r\n name: gif.title.trim() || gif.slug,\r\n value: {\r\n input,\r\n url: gif.images.downsized_medium.url,\r\n },\r\n preview: ``,\r\n }\r\n })\r\n }\r\n)\r\n\r\nlet formattedLink = await arg(\"Format to paste\", [\r\n {\r\n name: \"URL Only\",\r\n value: url,\r\n },\r\n {\r\n name: \"Markdown Image Link\",\r\n value: `![${input}](${url})`,\r\n },\r\n {\r\n name: \"HTML \",\r\n value: ``,\r\n },\r\n])\r\n\r\nsetSelectedText(formattedLink)\r\n\r\n```\r\n","value":"https://github.com/johnlindquist/kit/discussions/119","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"},{"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/112","url":"https://github.com/johnlindquist/kit/discussions/112","title":"Types are here!","name":"Types are here!","extension":".md","description":"Created by johnlindquist","resourcePath":"/johnlindquist/kit/discussions/112","createdAt":"2021-04-03T18:08:24Z","category":{"id":"MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyODIwMDgw","name":"Announcements","emoji":":loudspeaker:"},"id":"MDEwOkRpc2N1c3Npb24zMzA1MzEx","body":"Update (1.1.0-beta.86) adds a [`~/.kit/kit.d.ts`](https://github.com/johnlindquist/kit/blob/main/kit.d.ts) to allow better code hinting and completion.\r\n\r\n❗️After updating, you will need to manually \"link\" your `~/.kenv` to your `~/.kit` for the benefits (This will happen automatically for new users during install)\r\n\r\nMethod 1 - Install and run this script\r\n\r\n[Click to install link-kit](https://scriptkit.app/api/new?name=link-kit&url=https://gist.githubusercontent.com/johnlindquist/f238cb1b3a3ed97890657ccf154d12b1/raw/a488a8b6c331d527bb0433a6b8df9428263b85a0/link-kit.js)\r\n\r\n```js\r\nawait cli(\"install\", \"~/.kit\")\r\n```\r\n\r\nMethod 2 - In your terminal\r\n```bash\r\nPATH=~/.kit/node/bin ~/.kit/node/bin/npm --prefix ~/.kenv i ~/.kit\r\n```\r\n\r\nNow your scripts in your `~/.kenv/scripts` should have completion/hinting for globals included in the \"preloaded\" scripts.\r\n\r\n> I still need to add types for the helpers that load scripts from dirs `kit()`, `cli()`, etc.\r\n\r\nPlease let me know how it goes and if you have any questions. Thanks!","value":"https://github.com/johnlindquist/kit/discussions/112","img":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4"}]
diff --git a/apps/scriptkit/public/data/share.json b/apps/scriptkit/public/data/share.json
index 9f96e5c..68af146 100644
--- a/apps/scriptkit/public/data/share.json
+++ b/apps/scriptkit/public/data/share.json
@@ -1 +1 @@
-[{"avatar":"https://avatars.githubusercontent.com/u/91077547?u=912b5cbbf037a7082ac71e829d74641975d545ff&v=4","user":"MartinLednar","author":"Martin Lednár","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1327","url":"","title":"Jira monthly time logger","command":"jira-monthly-time-logger","content":"# About\r\n\r\nLog tasks you worked on and the total hours you worked for current month into jira.\r\n\r\n- [Gist link](https://gist.githubusercontent.com/MartinLednar/3e3b0e8b23c92734bf946662a4f4b502/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n- [Download link](https://scriptkit.com/api/new?name=jira-monthly-time-logger&url=https://gist.githubusercontent.com/MartinLednar/d702e7993415b04f9e85fd29b910e0cd/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n\r\n\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-08-09T13:12:04Z"},{"name":"tmux sesh","description":"","author":"Lazar Nikolov","twitter":"NikolovLazar","avatar":"https://avatars.githubusercontent.com/u/5396211?u=afab008a61b79b9bf8343040f532cf9cb3e974ee&v=4","user":"nikolovlazar","discussion":"https://github.com/johnlindquist/kit/discussions/1326","url":"https://gist.githubusercontent.com/nikolovlazar/21a78f492e117a5e0dca1685cb668f4d/raw/94e44f60ef8807250f365b996b610d8bb05fd311/tmux-sesh.js","title":"Attach to tmux session with Kitty terminal","command":"attach-to-tmux-session-with-kitty-terminal","content":"### Prerequisites:\r\n1. Install [Kitty](https://sw.kovidgoyal.net/kitty/)\r\n2. Add `kitty` to `PATH`: `sudo ln -s /Applications/kitty.app/Contents/MacOS/kitty /usr/local/bin/kitty` (assuming `/usr/local/bin` is in your `PATH`)\r\n3. Kit: `Sync $PATH from Terminal to Kit.app`\r\n\r\n\r\n\r\n\r\n\r\n[Open tmux-sesh in Script Kit](https://scriptkit.com/api/new?name=tmux-sesh&url=https://gist.githubusercontent.com/nikolovlazar/21a78f492e117a5e0dca1685cb668f4d/raw/94e44f60ef8807250f365b996b610d8bb05fd311/tmux-sesh.js\")\r\n\r\n```js\r\n// Name: tmux sesh\r\n// Description: Attach to a tmux session\r\n// Author: Lazar Nikolov\r\n// Twitter: @NikolovLazar\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst sessionsCmd = await $`tmux list-sessions`;\r\n\r\nlet sessions = sessionsCmd.stdout\r\n .split('\\n')\r\n .map((line) => line.split(':')[0])\r\n .filter((sesh) => !!sesh);\r\n\r\nlet choice = await arg('Attach to session:', sessions);\r\n\r\nawait $`kitty --hold sh -c \"tmux a -t ${choice}\"`;\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-08-08T14:35:41Z"},{"name":"Reveal password","shortcut":"cmd *","avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1322","url":"","title":"Reveal password","command":"reveal-password","content":"`| ******* |` cmd * → `| toto123 |`\r\n\r\n```ts\r\n// Name: Reveal password\r\n// Shortcut: cmd *\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet js = `\r\ndocument.activeElement.type = document.activeElement.type === 'password' ? 'text' : 'password';\r\n`;\r\n\r\nlet value = await applescript(`\r\ntell application \"Google Chrome\" to tell window 1\r\n\tget execute active tab javascript \"\r\n\r\n${js}\r\n\r\n\"\r\nend tell\r\n`);\r\n```\r\n-- https://gist.github.com/abernier/582e1458195ec34268305298e4b3b86b","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-31T06:08:58Z"},{"menu":"Gather Guest List","description":"","author":"Kent C. Dodds","twitter":"kentcdodds","avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1310","url":"","title":"Gather Town Guest Management","command":"gather-town-guest-management","content":"I use this script to manage who has access to join my [Gather.town](https://gather.town) space, so I don't have to manually approve folks joining, they don't need me to be there to join, and they can only access it if they're logged in and are on the list (they've purchased a ticket).\r\n\r\nVery cool thing I can throw together to solve my problems in an hour.\r\n\r\nFind the most up-to-date version in my repo: https://github.com/kentcdodds/.kenv/blob/main/scripts/gather-guest.ts\r\n\r\n\r\n[Open gather-guest in Script Kit](https://scriptkit.com/api/new?name=gather-guest&url=https://gist.githubusercontent.com/kentcdodds/592bd3aebb51971c3a968954ede061f6/raw/5bc5d065782e50f269c5ed9090976722fae50140/gather-guest.ts\")\r\n\r\n```js\r\n// Menu: Gather Guest List\r\n// Description: Handle the Guest List for Gather\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport {z} from 'zod'\r\n\r\nconst GuestObjectSchema = z.object({\r\n name: z.string().optional(),\r\n affiliation: z.string().optional(),\r\n role: z.string().optional(),\r\n})\r\nconst GuestsSchema = z.record(z.string().email(), GuestObjectSchema)\r\n\r\nconst GATHER_API_KEY = await env('GATHER_API_KEY', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_API_KEY',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Get a Gather API Key\r\n\r\n[app.gather.town/apikeys](https://app.gather.town/apikeys)\r\n `),\r\n )\r\n})\r\n\r\nconst GATHER_SPACE_ID = await env('GATHER_SPACE_ID', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_SPACE_ID',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Specify the Gather Space ID\r\n\r\nIt's everything after \"app/\" in this URL with \"/\" replaced by \"\\\\\":\r\n\r\nhttps://app.gather.town/app/BL0B93FK23T/example\r\n `),\r\n )\r\n})\r\n\r\nasync function go() {\r\n const params = new URLSearchParams({\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n })\r\n const rawGuests = await fetch(\r\n `https://gather.town/api/getEmailGuestlist?${params}`,\r\n ).then(r => r.json())\r\n\r\n const guests = GuestsSchema.parse(rawGuests)\r\n const choices = [\r\n {name: '➕ Add a guest', value: {type: 'add-guest'}},\r\n ...Object.entries(guests).map(([email, {name, affiliation, role}]) => ({\r\n name: `${email} (${name?.trim() || 'Unnamed'}, ${\r\n affiliation?.trim() || 'Unaffiliated'\r\n }, ${role?.trim() || 'No role'})`,\r\n value: {type: 'modify-guest', email},\r\n })),\r\n ]\r\n const rawSelection = await arg(\r\n {placeholder: 'Which guest would you like to modify?'},\r\n choices,\r\n )\r\n const SelectionSchema = z.union([\r\n z.object({\r\n type: z.literal('add-guest'),\r\n }),\r\n z.object({\r\n type: z.literal('modify-guest'),\r\n email: z.string(),\r\n }),\r\n ])\r\n const selection = SelectionSchema.parse(rawSelection)\r\n switch (selection.type) {\r\n case 'add-guest': {\r\n await addGuest()\r\n return go()\r\n }\r\n case 'modify-guest': {\r\n await modifyGuest(selection.email, guests)\r\n return go()\r\n }\r\n }\r\n}\r\n\r\nasync function addGuest() {\r\n const email = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: `What's the guests' email?`}))\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: {[email]: {}},\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\nasync function modifyGuest(\r\n email: string,\r\n guests: z.infer,\r\n) {\r\n const guest = guests[email]\r\n const action = await arg({placeholder: `What would you like to do?`}, [\r\n {name: 'Remove Guest', value: 'remove'},\r\n {name: `Change Guest Email (${email})`, value: 'change-email'},\r\n {\r\n name: `Change Guest Name (${guest.name?.trim() || 'Unnamed'})`,\r\n value: 'change-name',\r\n },\r\n {\r\n name: `Change Guest Affiliation (${\r\n guest.affiliation?.trim() || 'Unaffiliated'\r\n })`,\r\n value: 'change-affiliation',\r\n },\r\n {\r\n name: `Change Guest Role (${guest.role?.trim() || 'No role'})`,\r\n value: 'change-role',\r\n },\r\n {\r\n name: `Cancel`,\r\n value: 'cancel',\r\n },\r\n ])\r\n switch (action) {\r\n case 'remove': {\r\n delete guests[email]\r\n break\r\n }\r\n case 'change-email': {\r\n const newEmail = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: 'New Email'}))\r\n guests[newEmail] = guests[email]\r\n delete guests[email]\r\n email = newEmail\r\n break\r\n }\r\n case 'change-name': {\r\n const newName = await arg({placeholder: 'New Name'})\r\n if (newName) {\r\n guests[email].name = newName\r\n } else {\r\n delete guests[email].name\r\n }\r\n break\r\n }\r\n case 'change-affiliation': {\r\n const newAffiliation = await arg({\r\n placeholder: 'New Affiliation',\r\n })\r\n if (newAffiliation) {\r\n guests[email].affiliation = newAffiliation\r\n } else {\r\n delete guests[email].affiliation\r\n }\r\n break\r\n }\r\n case 'change-role': {\r\n const newRole = await arg({placeholder: 'New Role'})\r\n if (newRole) {\r\n guests[email].role = newRole\r\n } else {\r\n delete guests[email].role\r\n }\r\n break\r\n }\r\n case 'cancel': {\r\n return go()\r\n }\r\n }\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: guests,\r\n overwrite: true,\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\ngo()\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-13T19:05:44Z"},{"name":"Correct selection","description":"","author":"Ivan Rybnikov","twitter":null,"shortcut":"cmd option g","avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","discussion":"https://github.com/johnlindquist/kit/discussions/1309","url":"https://gist.githubusercontent.com/ivryb/646da11d9a5dbb2151a2053c4d510dd0/raw/8f9b16dd5c636ef6192c12b037ad80f7d84d0193/correct-selection-script.js","title":"Correct selection with ChatGPT","command":"correct-selection-with-chatgpt","content":"[Open Correct selection with ChatGPT in Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/646da11d9a5dbb2151a2053c4d510dd0/raw/8f9b16dd5c636ef6192c12b037ad80f7d84d0193/correct-selection-script.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-13T12:30:37Z"},{"name":"tiktok-images","description":"","author":"Trevor Atlas","twitter":"trevoratlas","threads":"trevor.atlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1308","url":"","title":"Resize and composite images for tiktok","command":"resize-and-composite-images-for-tiktok","content":"\r\n[Open tiktok-images in Script Kit](https://scriptkit.com/api/new?name=tiktok-images&url=https://gist.githubusercontent.com/trevor-atlas/9bc38697613660a228d89f45c5d5ead9/raw/cdbfee11f395fdf8fdaedacfafbb555a1db9bd65/tiktok-images.ts\")\r\n\r\n```js\r\n// Name: tiktok-images\r\n// Description: Resize images to fit TikTok's 9:16 aspect ratio and avoid being covered by the UI\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Threads: trevor.atlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst sharp = await npm('sharp');\r\nconst { getAverageColor } = await npm('fast-average-color-node');\r\n\r\nconst width = 1440;\r\nconst height = 2400;\r\nconst density = 72;\r\nconst scale = .8;\r\nconst validTypes = new Set(['image/png', 'image/jpeg', 'image/jpg']);\r\nconst outputPath = path.join(home(), 'Desktop', 'resized-images');\r\n\r\nasync function processImage(imageFilepath: string) {\r\n try {\r\n const averageColor = await getAverageColor(imageFilepath);\r\n const image = await sharp(imageFilepath)\r\n .withMetadata({ density })\r\n .resize({ fit: 'inside', width: Math.floor(width * scale), height: Math.floor(height * scale) })\r\n .png({ quality: 100 })\r\n\r\n .toBuffer();\r\n\r\n const color = averageColor.hex || 'black';\r\n\r\n // Add a matching background\r\n const background = await sharp({\r\n create: {\r\n channels: 4,\r\n background: color,\r\n width,\r\n height,\r\n },\r\n })\r\n .withMetadata({ density })\r\n .png({ quality: 100})\r\n .toBuffer();\r\n\r\n\r\n const res = await sharp(background)\r\n .composite([{ input: image, gravity: 'centre' }])\r\n .png({ quality: 100 })\r\n .toBuffer();\r\n\r\n return res;\r\n } catch (error) {\r\n console.error(error);\r\n throw error;\r\n }\r\n};\r\n\r\ninterface FileInfo {\r\n lastModified: number;\r\n lastModifiedDate: string;//\"2023-07-12T17:35:13.573Z\"\r\n name: string;\r\n path: string;//\"/Users/uname/Desktop/screenshots/Screenshot 2022-01-12 at 1.35.08 PM.png\"\r\n size: number;\r\n type: string;//\"image/png\"\r\n webkitRelativePath: string;\r\n}\r\n\r\n\r\ntry {\r\n const fileInfos: FileInfo[] = await drop('Drop images to resize');\r\n const imagePaths = fileInfos\r\n .filter(({type}) => validTypes.has(type))\r\n .map(fileInfo => fileInfo.path);\r\n\r\n if (!imagePaths.length) {\r\n await notify('No valid images found. Supports .png, .jpg, and .jpeg');\r\n exit();\r\n }\r\n\r\n await ensureDir(outputPath);\r\n\r\n for (const imagePath of imagePaths) {\r\n const image = await processImage(imagePath);\r\n const [filename] = path.basename(imagePath).split('.');\r\n const finalPath = path.join(outputPath, `${filename}-processed.png`);\r\n await writeFile(finalPath, image);\r\n console.log(`Resized ${finalPath}`);\r\n }\r\n\r\n await notify('Image(s) resized');\r\n} catch (error) {\r\n console.error(error);\r\n await notify('Error resizing images. Check the log for details.');\r\n}\r\n\r\nawait open(outputPath);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-12T22:37:36Z"},{"name":"Correct selection","description":"","author":"Ivan Rybnikov","twitter":null,"shortcut":"cmd option g","avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","discussion":"https://github.com/johnlindquist/kit/discussions/1307","url":"https://gist.githubusercontent.com/ivryb/9f63a1881b1827773682cdf7e404b05c/raw/9bce6cdf6dc54497f6d7020807fc1cc6bf405131/correct-selection.js","title":"Correct selection with ChatGPT","command":"correct-selection-with-chatgpt","content":"[Install \"Correct selection with ChatGPT\" to Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/9f63a1881b1827773682cdf7e404b05c/raw/9bce6cdf6dc54497f6d7020807fc1cc6bf405131/correct-selection.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-12T17:02:06Z"},{"name":"Decode Base64","description":"","author":null,"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1303","url":"https://gist.githubusercontent.com/ElTacitos/8eaa571a6026383c9ce71e593e31b598/raw/6aa4a043a4458b980111bfb76ebe0b435d90a524/base64-decode.js","title":"Decode a base64 string","command":"decode-a-base64-string","content":"Everything is in the title of this post, this script will allow you to decode a base64 string.\r\n\r\n[Open base64-decode in Script Kit](https://scriptkit.com/api/new?name=base64-decode&url=https://gist.githubusercontent.com/ElTacitos/8eaa571a6026383c9ce71e593e31b598/raw/6aa4a043a4458b980111bfb76ebe0b435d90a524/base64-decode.js\")\r\n\r\n```js\r\n// Name: Decode Base64\r\n// Description: Decode a base64 string and copy it to the clipboard\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet base64 = await arg(\"Enter base64 string to decode\")\r\nlet decoded = atob(base64)\r\n\r\nawait clipboard.writeText(decoded)\r\nawait div(md(`\r\n# ${decoded}\r\n`))\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-10T10:09:08Z"},{"menu":"De-Acronym-ify","description":"","author":"Trevor Atlas","twitter":"trevoratlas","shortcut":"cmd ctrl opt shift a","group":"work","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1296","url":"","title":"Replace user-defined acronyms with the full text","command":"replace-user-defined-acronyms-with-the-full-text","content":"\r\n[Open de-acronym in Script Kit](https://scriptkit.com/api/new?name=de-acronym&url=https://gist.githubusercontent.com/trevor-atlas/992682a54fa4ec44ccc8cc58e889e026/raw/f4ec3016bd7f8b1af4be65a64f3d500c19231e71/de-acronym.ts\")\r\n\r\n```js\r\n// Menu: De-Acronym-ify\r\n// Description: Replace acronyms with their full names\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Shortcut: cmd ctrl opt shift a\r\n// Group: work\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nlet text = '';\r\nconst clipboardValue = await paste();\r\nconst selection = await getSelectedText();\r\n\r\nif (selection) {\r\n text = selection;\r\n console.log('use selection', selection);\r\n}\r\n\r\nif (clipboardValue && !selection) {\r\n text = clipboardValue;\r\n console.log('use clipboard', text);\r\n}\r\n\r\nif (!text) {\r\n text = await arg('Enter text to de-acronym-ify');\r\n console.log('use prompt', text);\r\n}\r\n\r\nconst acronyms: Array<[string | RegExp, string]> = [\r\n ['PD', 'Product Design'],\r\n ['PM', 'Product Management'],\r\n ['JS', 'JavaScript'],\r\n ['TS', 'TypeScript'],\r\n];\r\n\r\nconst result = acronyms.reduce(\r\n (acc, [acronym, expansion]) => acc.replace(acronym, expansion),\r\n text\r\n);\r\n\r\nif (!selection) {\r\n copy(result);\r\n} else {\r\n await setSelectedText(result);\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-30T20:35:34Z"},{"name":"humanlike typing","description":"","author":"Trevor Atlas","twitter":"trevoratlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1295","url":"","title":"Type clipboard like a human","command":"type-clipboard-like-a-human","content":"I haven't quite dialed in the random delays as well as I'd like, but it gets the job done :]\r\n\r\n[Open humanlike-typing in Script Kit](https://scriptkit.com/api/new?name=humanlike-typing&url=https://gist.githubusercontent.com/trevor-atlas/17746a243dd9bbfa8062d8fb86b5fc20/raw/45b2a5cb769b76db73f70579edf28b469ba194bd/humanlike-typing.ts\")\r\n\r\n```js\r\n// Name: humanlike typing\r\n// Description: Type the contents of your clipboard as if you were a human\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\n\r\nawait applescript(String.raw`\r\nset texttowrite to the clipboard as text\r\ntell application \"System Events\"\r\n repeat with i from 1 to count characters of texttowrite\r\n if (character i of texttowrite) is equal to linefeed or (character i of texttowrite) is equal to return & linefeed or (character i of texttowrite) is equal to return then\r\n keystroke return\r\n else\r\n keystroke (character i of texttowrite)\r\n end\r\n if (character i of texttowrite) is equal to \" \" then\r\n delay (random number from 0.01 to 0.1)\r\n else if (character i of texttowrite) is equal to \"\\n\" then\r\n delay (random number from 0.1 to 0.3)\r\n else\r\n delay (random number from 0.01 to 0.05)\r\n end\r\n end repeat\r\nend tell\r\n`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-30T20:32:02Z"},{"name":"vpn","author":"Trevor Atlas","twitter":"trevoratlas","schedule":"*/15 * * * *","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1294","url":"","title":"Connect to GlobalProtect VPN if not connected","command":"connect-to-globalprotect-vpn-if-not-connected","content":"\r\n[Open vpn in Script Kit](https://scriptkit.com/api/new?name=vpn&url=https://gist.githubusercontent.com/trevor-atlas/45ea4ba63553e81facc93105cf52dc65/raw/a983e86ac4885afeff3928e268ad780020beffda/vpn.ts\")\r\n\r\n```js\r\n// Name: vpn\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Schedule: */15 * * * *\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\napplescript(`\r\ntell application \"System Events\" to tell process \"GlobalProtect\"\r\n\tset connectionStatus to get help of every menu bar item of menu bar 2\r\n\tif item 1 of connectionStatus = \"Not Connected\" then\r\n\t\tclick menu bar item 1 of menu bar 2 -- Activates the GlobalProtect \"window\" in the menubar\r\n\t\ttry\r\n\t\t\tclick button \"Connect\" of window 1\r\n\t\tend try\r\n\t\tclick menu bar item 1 of menu bar 2 -- This will close the GlobalProtect \"window\" after clicking Connect/Disconnect. This is optional.\r\n\tend if\r\nend tell\r\n`);\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-30T19:36:57Z"},{"name":"Search Open PRs","description":"","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1284","url":"https://gist.githubusercontent.com/mabry1985/7cf5cec8d5913948aeda070f51ecfe4d/raw/64c98abcade33cdeb54604f29a06c55f05d374f1/search-open-pr.js","title":"Search Open PRs","command":"search-open-prs","content":"Change the owner and repo name to desired repo and get to reviewing!\r\n\r\n[Open search-open-pr in Script Kit](https://scriptkit.com/api/new?name=search-open-pr&url=https://gist.githubusercontent.com/mabry1985/7cf5cec8d5913948aeda070f51ecfe4d/raw/64c98abcade33cdeb54604f29a06c55f05d374f1/search-open-pr.js\")\r\n\r\n```js\r\n// Name: Search Open PRs\r\n// Description: Search open PRs in a repo\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst fetch = await npm(\"node-fetch\");\r\nconst variables = {\r\n owner: \"knapsack-labs\",\r\n repoName: \"app-monorepo\",\r\n};\r\n\r\nlet token = await env(\"GITHUB_AUTH_TOKEN\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst query = `\r\nquery getPrs($owner: String!, $repoName: String!) {\r\n repository(owner: $owner, name: $repoName) {\r\n pullRequests(last: 100, states: OPEN) {\r\n nodes {\r\n body\r\n createdAt\r\n mergeable\r\n number\r\n state\r\n title\r\n updatedAt\r\n url\r\n author {\r\n avatarUrl\r\n login\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n`;\r\n\r\nasync function getPrs() {\r\n return fetch(\"https://api.github.com/graphql\", {\r\n headers: {\r\n authorization: `bearer ${token}`,\r\n },\r\n method: \"POST\",\r\n body: JSON.stringify({ query, variables }),\r\n })\r\n .then((res) => res.json())\r\n .catch((err) => {\r\n console.log(err);\r\n exit();\r\n });\r\n}\r\nconst prs = await getPrs();\r\nconst openPRs = prs.data.repository.pullRequests.nodes;\r\nconst sortedPrs = openPRs.sort(\r\n (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)\r\n);\r\nconst pr = await arg(\r\n {\r\n placeholder: `Select a PR to view`,\r\n },\r\n sortedPrs.map((pr) => {\r\n return {\r\n name: `${pr.number} - ${pr.title}`,\r\n preview: () =>\r\n `
\r\n
${pr.number} - ${pr.title}
\r\n \r\n
Ready to Merge: ${pr.mergeable === \"MERGEABLE\" ? \"✅\" : \"⛔\"}
\r\n
${md(pr.body)}
\r\n \r\n
Author: ${pr.author.login}
\r\n \r\n \r\n
`,\r\n value: pr.number,\r\n };\r\n })\r\n);\r\n\r\nconst prInfo = sortedPrs.find((p) => p.number === pr);\r\nbrowse(prInfo.url);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-21T20:59:28Z"},{"name":"JS Expression","description":"","avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1283","url":"https://gist.githubusercontent.com/alkene0005/25bd8b0b560fdc4d1b582bf1c6a4ed55/raw/af15c0765269eb22b6aaa7ac207ed152c0088d3c/js-expression.js","title":"A simple calculator using js expressions","command":"a-simple-calculator-using-js-expressions","content":"\r\n[Open js-expression in Script Kit](https://scriptkit.com/api/new?name=js-expression&url=https://gist.githubusercontent.com/alkene0005/25bd8b0b560fdc4d1b582bf1c6a4ed55/raw/af15c0765269eb22b6aaa7ac207ed152c0088d3c/js-expression.js\")\r\n\r\n```js\r\n// Name: JS Expression\r\n// Description: I prefer to define it as a simple calculator\r\n\r\n// Global Objects\r\nlet arr = [1, 2, 3, 4, 5]\r\nlet obj = {name: 'Mike', age: 20}\r\n\r\n// Global Functions\r\nlet {\r\n ceil, floor, round, trunc, abs, PI,\r\n sin, cos, tan, log, log2, log10, exp, sqrt, cbrt, pow\r\n} = Math\r\n\r\n// Factorial\r\nlet fact = num => _.reduce(_.range(1, num + 1), (acc, i) => acc * i, 1)\r\n\r\nlet selected = await arg({\r\n placeholder: 'Expression ...',\r\n enter: 'Copy & Exit',\r\n shortcuts: [{\r\n name: 'Repalce', key: 'cmd+r', bar: 'right', onPress: async (input, {focused}) => {\r\n setInput(evalExp(input))\r\n }\r\n }]\r\n}, async (input) => {\r\n let res = input ? evalExp(input) : ''\r\n return md(`~~~javascript\\n${res}\\n~~~`)\r\n})\r\n\r\nif (selected) await copy(evalExp(selected))\r\n\r\nfunction evalExp(input) {\r\n let value = eval(`(${input})`)\r\n if (typeof value == 'number') return (value % 1 != 0 ? value.toFixed(2) : value) + ''\r\n if (typeof value == 'array') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'object') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'function') return ''\r\n}\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-18T17:37:10Z"},{"name":"Steam","avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1281","url":"https://gist.githubusercontent.com/alkene0005/17d2c82c34122a023841c09aae248800/raw/c666139ec1024fb0d3b1df3f92071e57d9713626/web-steam-game.js","title":"Quick Search Steam Game","command":"quick-search-steam-game","content":"\r\n[Open web-steam-game in Script Kit](https://scriptkit.com/api/new?name=web-steam-game&url=https://gist.githubusercontent.com/alkene0005/17d2c82c34122a023841c09aae248800/raw/c666139ec1024fb0d3b1df3f92071e57d9713626/web-steam-game.js\")\r\n\r\n```js\r\n// Name: Steam\r\n\r\nimport axios from 'axios'\r\nimport cheerio from 'cheerio'\r\n\r\n// Language-dependent configuration\r\nconst cc = 'US'\r\nconst l = 'english'\r\n\r\nfunction buildResult(value, image, title) {\r\n return {\r\n name: 'abc',\r\n value: value,\r\n html: `\r\n
\r\n \r\n
${title}
\r\n
open
\r\n
\r\n `,\r\n }\r\n}\r\n\r\nlet url = await arg('Keyword ...', async keyword => {\r\n if (keyword.trim() == '') return []\r\n let {data} = await axios.get(\r\n 'https://store.steampowered.com/search/suggest?term=' + keyword +\r\n '&f=games&cc=' + cc + '&realm=1&l=s' + l + '&v=19040599&excluded_content_descriptors%5B%5D=3' +\r\n '&excluded_content_descriptors%5B%5D=4&use_store_query=1&use_search_spellcheck=1&search_creators_and_tags=1'\r\n );\r\n let $ = cheerio.load(data);\r\n let games = $('a').get().map(aTag => {\r\n if ($(aTag).hasClass('match_app')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let price = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${price}`)\r\n }\r\n if ($(aTag).hasClass('match_tag')) {\r\n let name = $(aTag).find('.match_name span').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, 'https://pbs.twimg.com/profile_images/861662902780018688/SFie8jER_x96.jpg', `${name} - ${count}`)\r\n }\r\n if ($(aTag).hasClass('match_creator')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${count}`)\r\n }\r\n });\r\n return games.filter(x => x);\r\n});\r\n\r\nawait $`open ${url}`\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-18T03:49:41Z"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1272","url":"https://gist.githubusercontent.com/mabry1985/54c10fa4594fb5a5edcf65c1db55b44b/raw/8460dafe391bd8bb09593e35e2fb89764d27f521/palm-chat.js","title":"Google PaLM2 Chat","command":"google-palm2-chat","content":"The LLM is still in early access but you can sign up for the waitlist [here](https://developers.generativeai.google/)\r\n\r\n[Open palm-chat in Script Kit](https://scriptkit.com/api/new?name=palm-chat&url=https://gist.githubusercontent.com/mabry1985/54c10fa4594fb5a5edcf65c1db55b44b/raw/8460dafe391bd8bb09593e35e2fb89764d27f521/palm-chat.js\")\r\n\r\n```js\r\nlet { GoogleAuth } = await import(\"google-auth-library\");\r\nlet { DiscussServiceClient } = await import(\"@google-ai/generativelanguage\");\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst MODEL_NAME = \"models/chat-bison-001\";\r\nconst API_KEY = await env(\"PALM_API_KEY\", {\r\n hint: `Signup for waitlist here here`,\r\n});\r\n\r\nconst client = new DiscussServiceClient({\r\n authClient: new GoogleAuth().fromAPIKey(API_KEY),\r\n});\r\n\r\nconst config = {\r\n model: MODEL_NAME,\r\n temperature: 0.75,\r\n candidateCount: 1,\r\n top_k: 40,\r\n top_p: 0.95,\r\n};\r\n\r\nconst chatHistory = [];\r\n\r\nconst generateText = async (text) => {\r\n chatHistory.push({ content: text });\r\n const response = await client.generateMessage({\r\n ...config,\r\n prompt: {\r\n context: \"You are a funny and helpful assistant.\",\r\n messages: chatHistory,\r\n },\r\n });\r\n\r\n log(response);\r\n log(response[0].filters);\r\n if (response[0].filters.length > 0) {\r\n return `The model has rejected your input. Reason: ${response[0].filters[0].reason}`;\r\n } else {\r\n chatHistory.push({ content: response[0].candidates[0].content });\r\n return response[0].candidates[0].content;\r\n }\r\n};\r\n\r\nawait chat({\r\n onSubmit: async (input) => {\r\n setLoading(true);\r\n try {\r\n const response = await generateText(input);\r\n let message = md(response);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, message);\r\n } catch (e) {\r\n console.log(e);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, md(\"Error: \" + e.message));\r\n }\r\n setLoading(false);\r\n },\r\n});\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-06T01:54:00Z"},{"name":"Static to Dynamic","description":"","author":"Josh Mabry","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1271","url":"https://gist.githubusercontent.com/mabry1985/13b951630f05eebc35c66d8e706dee70/raw/70fb4529876ef97fd18351793d329afca945079e/static-to-dynamic.js","title":"Static to dynamic import converter","command":"static-to-dynamic-import-converter","content":"I got tired of typing out the conversion when pulling in script examples so I made this quick script to convert them\r\n\r\n[Open static-to-dynamic in Script Kit](https://scriptkit.com/api/new?name=static-to-dynamic&url=https://gist.githubusercontent.com/mabry1985/13b951630f05eebc35c66d8e706dee70/raw/70fb4529876ef97fd18351793d329afca945079e/static-to-dynamic.js\")\r\n\r\n```js\r\n// Name: Static to Dynamic\r\n// Description: Convert static import to dynamic import\r\n// e.g. import { Foo } from \"bar\";\r\n// to let { Foo } = await import(\"bar\");\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst text = await getSelectedText();\r\n\r\nfunction convertImportString(input) {\r\n const importRegex = /import\\s+({[^}]+})\\s+from\\s+\"([^\"]+)\";/;\r\n\r\n if (!importRegex.test(input)) {\r\n throw new Error(\"Invalid import string format\");\r\n }\r\n\r\n const [_, importList, modulePath] = input.match(importRegex);\r\n const output = `let ${importList} = await import(\"${modulePath}\");`;\r\n return output;\r\n}\r\n\r\nconst output = convertImportString(text);\r\n\r\nawait setSelectedText(output);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-04T18:07:43Z"},{"menu":"Password Manager","description":"","shortcut":"command shift ]","author":"Rohit Kumar Saini","avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1262","url":"","title":"A simple Password Manager","command":"a-simple-password-manager","content":"# Password Manager\r\nA simple password manager to add new passwords and copy from one of the existing list of passwords. Passwords are saved after encryption.\r\n\r\n\r\n\r\n[Open password-manager in Script Kit](https://scriptkit.com/api/new?name=password-manager&url=https://gist.githubusercontent.com/rockingrohit9639/586628e63330061cdeaff35cbc7dec05/raw/231215f7064ce763ffd4b2aa93ebb00c0341f080/password-manager.ts\")\r\n\r\n\r\n```js\r\n// Menu: Password Manager\r\n// Description: Manager all your passwords justing using few keys\r\n// Shortcut: command shift ]\r\n// Author: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst { nanoid } = await npm(\"nanoid\");\r\nconst Cryptr = await npm(\"cryptr\");\r\n\r\nconst CRYPTR_KEY = await env(\"CRYPTR_KEY\");\r\nconst cryptr = new Cryptr(CRYPTR_KEY);\r\n\r\nconst { passwords, write } = await db(\"passwords\", { passwords: [] });\r\n\r\ntype Option = {\r\n name: string;\r\n description: string;\r\n value: \"ADD_NEW_PASSWORD\" | \"COPY_PASSWORD\";\r\n};\r\n\r\nconst PM_OPTIONS: Option[] = [\r\n {\r\n name: \"Add New Password\",\r\n description: \"Add a new password to the database\",\r\n value: \"ADD_NEW_PASSWORD\",\r\n },\r\n {\r\n name: \"Copy Password\",\r\n description: \"Copy one of the saved passwords\",\r\n value: \"COPY_PASSWORD\",\r\n },\r\n];\r\n\r\nconst choice: Option[\"value\"] = await arg(\r\n \"What would you like to do?\",\r\n PM_OPTIONS\r\n);\r\n\r\n/** Doing operation on basis of choice */\r\nif (choice === \"ADD_NEW_PASSWORD\") {\r\n addNewPassword();\r\n}\r\n\r\nif (choice === \"COPY_PASSWORD\") {\r\n listAndCopyPassword();\r\n}\r\n\r\nasync function addNewPassword() {\r\n const title = await arg({\r\n placeholder: \"Title\",\r\n hint: \"Title for which your password belongs e.g Facebook etc.\",\r\n });\r\n const password = await arg({\r\n placeholder: \"Password\",\r\n hint: `Password you want to save for ${title}`,\r\n });\r\n\r\n /** Encrypting the password */\r\n const encryptedPassword = cryptr.encrypt(password);\r\n\r\n const id = nanoid(5);\r\n const newPassword = { id, title, password: encryptedPassword };\r\n passwords.push(newPassword);\r\n\r\n /** Saving the password in db */\r\n await write();\r\n notify(`Password for ${title} added successfully!`);\r\n}\r\n\r\nasync function listAndCopyPassword() {\r\n const passwordToCopy = await arg(\r\n \"Which password would you like to copy ?\",\r\n () =>\r\n passwords.map(({ title, password }) => ({ name: title, value: password }))\r\n );\r\n\r\n /** Decrypting the password */\r\n const decryptedPassword = cryptr.decrypt(passwordToCopy);\r\n\r\n /** Copying the password to clipboard */\r\n copy(decryptedPassword);\r\n notify(\"Password copied to you clipboard!\");\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-21T04:44:25Z"},{"name":"Pomodoro","description":"","avatar":"https://avatars.githubusercontent.com/u/597015?u=1e764f789e382da7c81ee37ef3e1060bac244310&v=4","user":"LukeCarrier","author":"Luke Carrier","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1261","url":"","title":"Silly Pomodoro timer","command":"silly-pomodoro-timer","content":"Just a small hack to replace the many menu bar applications I've used over the years, and an excuse to have a play with Script Kit. I'm glad I did -- it's awesome 😁 \r\n\r\n[Open pomodoro in Script Kit](https://scriptkit.com/api/new?name=pomodoro&url=https://gist.githubusercontent.com/LukeCarrier/b5800f573f43fc7acf4ea327f6e396b4/raw/d75fe7d8b4428500457cb2e6de3e2b11e1c9353c/pomodoro.ts\")\r\n\r\n```js\r\n// Name: Pomodoro\r\n// Description: A Pomodoro timer, right here!\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst HOUR_MIN = 60;\r\nconst MIN_SEC = 60;\r\nconst SEC_MS = 1000;\r\n\r\nconst WORK_INTERVAL_SECS = 25 * 60;\r\nconst REST_INTERVAL_SECS = 5 * 60;\r\n\r\nconst WORK_INTERVAL_ICON = \"🍅\";\r\nconst REST_INTERVAL_ICON = \"🏝️\";\r\nconst COMPLETE_ICON = \"🎉\";\r\n\r\nconst WIDGET_HTML = `\r\n
\r\n {{icon}}\r\n
\r\n
\r\n
{{goal}}
\r\n
{{timer}}
\r\n
\r\n`;\r\nconst DING_JS = `new Audio(\"../kenvs/personal/assets/ding.ogg\").play();`;\r\nconst DING_SECS = 5;\r\n\r\nfunction formatTimeRemaining(seconds: number): string {\r\n const totalMinutes = Math.floor(seconds / HOUR_MIN);\r\n const formatSeconds = String(seconds % MIN_SEC).padStart(2, \"0\");\r\n const formatMinutes = String(totalMinutes % MIN_SEC).padStart(2, \"0\");\r\n return `${formatMinutes}:${formatSeconds}`;\r\n}\r\n\r\nconst goal = await arg(\"What's your goal this interval?\")\r\n\r\nconst timerWidget = await widget(WIDGET_HTML, {\r\n title: \"Pomodoro\",\r\n state: { icon: \"\", goal: \"\", timer: \"\" },\r\n\r\n containerClass: \"p-6 max-w-sm mx-auto rounded-xl shadow-lg flex items-center space-x-4\",\r\n alwaysOnTop: true,\r\n preventEscape: true,\r\n minimizable: false,\r\n maximizable: false,\r\n fullscreenable: false,\r\n opacity: 0.45,\r\n\r\n // If these are below the minimum size of a widget on macOS (160x120) the\r\n // widget appears as a small white box without any content until manually\r\n // resized.\r\n width: 340,\r\n height: 120,\r\n});\r\n\r\nfunction doInterval(icon: string, goal: string, interval_secs: number): Promise {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(interval_secs) });\r\n\r\n return new Promise((resolve) => {\r\n const startTime = new Date().getTime();\r\n const timerInterval = setInterval(() => {\r\n const thisTime = new Date().getTime();\r\n const elapsedSeconds = Math.round((thisTime - startTime) / SEC_MS);\r\n const remainingSeconds = interval_secs - elapsedSeconds;\r\n if (remainingSeconds >= 0) {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(remainingSeconds) });\r\n } else {\r\n clearInterval(timerInterval);\r\n timerWidget.executeJavaScript(DING_JS).finally(() => {\r\n resolve();\r\n });\r\n }\r\n }, 1000);\r\n });\r\n}\r\n\r\nawait doInterval(WORK_INTERVAL_ICON, goal, WORK_INTERVAL_SECS);\r\nawait doInterval(REST_INTERVAL_ICON, `Break after ${goal}`, REST_INTERVAL_SECS);\r\ntimerWidget.setState({ icon: COMPLETE_ICON, goal: `${goal} all done!`, timer: \"That's another interval complete.\" });\r\nsetTimeout(() => timerWidget.close(), DING_SECS * 1000);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-20T19:31:28Z"},{"name":"Json To Yaml Converter","author":"Eddie","twitter":"schmedu_","avatar":"https://avatars.githubusercontent.com/u/8198764?u=8159fd937f0836c330f0aed6c4d518c48461072e&v=4","user":"Schmedu","discussion":"https://github.com/johnlindquist/kit/discussions/1259","url":"","title":"JSON 2 YAML","command":"json-2-yaml","content":"[Open json2yaml in Script Kit](https://scriptkit.com/api/new?name=json2yaml&url=https://gist.githubusercontent.com/Schmedu/c904124d7a9cd4b9fd25485c9d8c36d0/raw/75255898c5293cbe648e1f7c521bc5a93c120e7b/json2yaml.ts\")\r\n\r\n```js\r\n// Name: Json To Yaml Converter\r\n// Author: Eduard Uffelmann\r\n// Twitter: @schmedu_\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport * as yaml from \"js-yaml\";\r\n\r\nlet filePath = await getSelectedFile();\r\nlet content = await readJson(filePath);\r\n\r\nlet result = yaml.dump(content);\r\n\r\nlet todo = await mini(\"What to do?\", [\"Copy\", \"Save\"]);\r\nif (todo === \"Copy\") {\r\n await copy(result);\r\n} else {\r\n await writeFile(filePath.replace(\".json\", \".yaml\"), result);\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-17T10:31:51Z"},{"name":"Get GitHub Commits Messages Since Tag","description":"","author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/1254","url":"","title":"Get GitHub Commit Messages Since Tag","command":"get-github-commit-messages-since-tag","content":"\r\n[Open get-commits in Script Kit](https://scriptkit.com/api/new?name=get-commits&url=https://gist.githubusercontent.com/johnlindquist/e56b9ad663cd56c947cc528c5f1c9f96/raw/a10ac8f6d49ebf961fd08013aec4f9b998e1024c/get-commits.ts)\r\n\r\n```js\r\n// Name: Get GitHub Commits Messages Since Tag\r\n// Description: Get all commit messages since a tag\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Octokit } = await import(\"@octokit/rest\")\r\n\r\nlet ownerRepo = await arg(\"Enter username/repo. Example: johnlindquist/kit\")\r\nlet [owner, repo] = ownerRepo.split(\"/\")\r\nlet tag = await arg(\"Tag. Example: v1.54.53\")\r\n\r\nlet client = new Octokit({\r\n auth: await env(\"GITHUB_PERSONAL_ACCESS_TOKEN\"),\r\n})\r\n\r\nlet page = 1\r\nlet hasMorePages = true\r\nlet messages = []\r\n\r\nlet ref = null\r\nlet tagPage = 1\r\nwhile (!ref) {\r\n let listTags = await client.repos.listTags({\r\n owner,\r\n repo,\r\n per_page: 100,\r\n name: tag,\r\n page: tagPage,\r\n })\r\n\r\n tagPage++\r\n ref = listTags.data.find(t => t.name === tag).commit.sha\r\n}\r\n\r\nlet commit = await client.repos.getCommit({\r\n owner,\r\n repo,\r\n ref,\r\n})\r\n\r\nlet since = commit.data.commit.author.date\r\n\r\nwhile (hasMorePages) {\r\n let data = await client.repos.listCommits({\r\n owner,\r\n repo,\r\n since,\r\n per_page: 100,\r\n page: page,\r\n })\r\n\r\n hasMorePages = data.data.length === 100\r\n messages = messages.concat(data.data.map(c => c.commit.message))\r\n\r\n page++\r\n}\r\n\r\nlet text = messages.join(\"\\n\\n\")\r\n\r\nif (env?.[\"GITHUB_SCRIPTKIT_TOKEN\"]) {\r\n let response = await createGist(text, {\r\n description: `Commit messages since ${tag}`,\r\n isPublic: false,\r\n fileName: \"commit-messages.txt\",\r\n })\r\n\r\n open(response.html_url)\r\n\r\n debugger\r\n} else {\r\n await editor(text)\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-10T16:08:12Z"},{"description":"","author":"Pierre Borckmans","shortcut":"ctrl opt cmd b","avatar":"https://avatars.githubusercontent.com/u/5610359?u=7019998d8df8daf3c7dda50ab3682fce7d43aad2&v=4","user":"pierre-borckmans","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1253","url":"","title":"Drive homebrew through Script kit","command":"drive-homebrew-through-script-kit","content":"Here's a small script that lets the user drive Homebrew from kit script:\r\n- list installed formulae / casks\r\n- install a new formula / cask from a list of all the ones available, minus the ones already installed\r\n- uninstall an existing formula/cask\r\n\r\nHope it's useful\r\n\r\n```// Name: Homebrew menu\r\n// Description: Drive homebrew through Kit-script\r\n// Author: Pierre Borckmans\r\n\r\n// shortcut: ctrl opt cmd b\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst BREW_CMD = '/opt/homebrew/bin/brew'\r\n\r\n\r\nconst cmdList = async (cmd) => (await cmd)._stdout.split(\"\\n\").slice(0, -1)\r\n\r\n\r\nconst getInstalled = async (type) => {\r\n return await cmdList($`${BREW_CMD} list --${type} -1`)\r\n}\r\n\r\nconst getAvailable = async (type) => {\r\n const items = (await cmdList($`${BREW_CMD} ${type} -1`)).map(o => ({\r\n name: o,\r\n value: o,\r\n description: type.slice(0, -1)\r\n }))\r\n const alreadyInstalled = await getInstalled(type.slice(0, -1))\r\n return items.filter(i => !alreadyInstalled.find(ai => ai === i.value));\r\n}\r\n\r\nconst install = async () => {\r\n const packageName = await arg(\"Enter a package to install\", [...await getAvailable(\"formulae\"), ...await getAvailable(\"casks\")])\r\n await $`${BREW_CMD} install ${packageName}`\r\n}\r\n\r\nconst uninstall = async () => {\r\n const packageName = await arg(\"Choose a package to uninstall\", [...await list(\"formula\"), ...await list(\"cask\")])\r\n await $`${BREW_CMD} uninstall ${packageName}`\r\n}\r\n\r\nconst menu = async () => {\r\n const menuOptions = [\r\n { \r\n value: \"formulae\",\r\n name: \"List installed formulae\"\r\n },\r\n { \r\n value: \"casks\",\r\n name: \"List installed casks\"\r\n },\r\n { \r\n value: \"install\",\r\n name: \"Install a cask or formula\"\r\n },\r\n { \r\n value: \"uninstall\",\r\n name: \"Uninstall a cask or formula\"\r\n },\r\n ]\r\n const menuChoice = await arg(\"Select an option\", menuOptions)\r\n\r\n switch (menuChoice) {\r\n case \"formulae\":\r\n await arg(`Homebrew formulas`, await getInstalled(\"formula\"))\r\n break\r\n case \"casks\":\r\n await arg(`Homebrew casks`, await getInstalled(\"cask\"))\r\n break\r\n case \"install\":\r\n await install()\r\n break \r\n case \"uninstall\":\r\n await uninstall()\r\n break \r\n default:\r\n break;\r\n }\r\n await menu()\r\n}\r\n\r\nawait menu();\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-10T14:31:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/22137371?u=22b3858822ffbf8ddce68b5c2e000a9910dbb934&v=4","user":"ScytheDraven47","author":"Ben Rogers-McKee","twitter":"ScytheDraven47","discussion":"https://github.com/johnlindquist/kit/discussions/1251","url":"","title":"Bitwarden Passwords via CLI","command":"bitwarden-passwords-via-cli","content":"I figured it'd be nice to have a Bitwarden Script for use outside of browsers, and it made for a good first mini project.\r\nIt uses [@bitwarden/cli](https://www.npmjs.com/package/@bitwarden/cli) via NPM, though I'm looking at doing an API version as well.\r\n\r\n[Code/gist here](https://gist.github.com/ScytheDraven47/0605ea9475778ae9cc2279c6fd07ad2e)\r\n\r\n`Enter` copies password\r\n`Ctrl+Enter` copies username\r\n`Ctrl+Shift+Enter` pastes username, then tabs once, then pastes password (won't work for all use cases, but figured it's nice to have)\r\n\r\nThis script does not save user credentials, but saves a session key to prevent frequent logging in.\r\n\r\nCurrently missing 2FA via email and YubiKey.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-09T08:45:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1249","url":"","title":"Multichoice for the `arg` function","command":"multichoice-for-the-arg-function","content":"I wrapped the `arg` function to allow multi-selection.\r\nJust replace `arg` with `multiArg` and you're good to go. ✨😊\r\nProvide a 3rd argument to customize item templates.\r\n\r\n[See code here](https://gist.github.com/BeSpunky/468b2e790ba9e32a73a3717dc876bdc4)\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236700932-ad5410fa-f717-4dea-9657-b78a0fc4a6c5.mp4\r\n\r\n`Enter`: Toggles Selection\r\n`Ctrl+Enter`: Submits the results\r\n\r\n**Known issues:**\r\n* When the list is longer than the window, list jumps occur. See #1248 \r\n* The `input` parameter passed into the choice factory function (2nd argument of `arg`) is always `''` and doesn't reflect user input.\r\n* List is filtered, but the default template doesn't highlight fuzzy search matches like the original one.\r\n\r\nEnjoy 🥂","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-07T20:29:14Z"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1247","url":"","title":"Efficient rebuild of scripts when `lib` files change","command":"efficient-rebuild-of-scripts-when-lib-files-change","content":"This script watches the `lib` folder, and when changes to `ts` files are made, it does 2 things:\r\n1. Create/update a dependency graph of `libFilePath -> dependantScriptPaths[]`\r\n2. Touches all script files that depend on the changed `lib` file to trigger rebuild.\r\n\r\nhttps://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a\r\n\r\n**TLDR**\r\nCurrently, ScriptKit only rebuilds scripts if it detects changes to the `scripts` folder.\r\nIf you extract your reusable parts and put them into the `lib` folder, ScriptKit doesn't pick up on changes to those files.\r\nThe manual way to overcome this is to save your script file again and trigger rebuild.\r\n\r\nNo more... :)\r\n\r\nActually, this is a 3 scripts solution:\r\n1. [`update-script-dependency-graphs`](https://gist.github.com/BeSpunky/c9139cdedfa349c501a70febea3c46d5): Partially rebuilds the graph if a triggering file has been provided, otherwise completely rebuilds it.\r\n2. [`watch-libs`](https://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a): Watches lib files, partially rebuild the graph using `update-script-dependency-graph`, then touch the scripts.\r\n3. [`watch-scripts`](https://gist.github.com/BeSpunky/1ce1ad4e29b339bec11cd2d416cb676c): Watch script files, partially rebuild the graph\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T21:38:13Z"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1246","url":"","title":"Create a Gist for you script and it's lib dependencies","command":"create-a-gist-for-you-script-and-its-lib-dependencies","content":"## Watch how I publish the script that publishes scripts and their dependencies to Gist... 😄\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236570763-9c84163f-c4f3-46d6-943f-537724db2b2e.mp4\r\n\r\n\r\nHere's the Gist of it:\r\nhttps://gist.github.com/BeSpunky/ff5dcb62887cbee686dd6c3ba31cabb5\r\n\r\n**TLDR**\r\nAs I go playing with ScriptKit, I started using the `lib` folder to centralize reusable functionality.\r\nThis made my scripts difficult to share, as they have nested dependencies which I would've had to add manually to my Gists.\r\nWell no more... 💪\r\n\r\nThis script let's you choose one of your scripts, reads it and recursively extracts `lib` dependencies, then publishes a new gist with the script and the dependencies.\r\n\r\nEnjoy 😊","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T21:23:15Z"},{"name":"Prompt Anything","description":"","author":"Josh Mabry","twitter":null,"shortcut":"alt shift enter","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1245","url":"https://gist.githubusercontent.com/mabry1985/482fcf46ae66d79348d63096e00fb5d5/raw/2114b2c4644787ec77ae9f39adff333ecc23f864/prompt-anywhere.js","title":"Prompt Anywhere v2","command":"prompt-anywhere-v2","content":"[Open prompt-anywhere in Script Kit](https://scriptkit.com/api/new?name=prompt-anywhere&url=https://gist.githubusercontent.com/mabry1985/482fcf46ae66d79348d63096e00fb5d5/raw/2114b2c4644787ec77ae9f39adff333ecc23f864/prompt-anywhere.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as quick-prompt.js and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n //#########\r\n // Helpers\r\n //########\r\n // exit script on cancel\r\n const cancelChat = () => {\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Paste text to highlighted text and exit script\r\n * @param {*} text\r\n */\r\n const pasteTextAndExit = async (text) => {\r\n await setSelectedText(text);\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Copy text to clipboard and exit script\r\n * @param {*} text\r\n */\r\n const copyToClipboardAndExit = async (text) => {\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n };\r\n\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // which works, but would be nice to also have ESC work\r\n ignoreBlur: false,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // have paste on text on submit?\r\n // onSubmit: () => pasteTextAndExit(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n await pasteTextAndExit(currentMessage);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // @TODO still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all of the actions like copy, paste, etc\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => await copyToClipboardAndExit(state),\r\n onSubmit: async (state) => await pasteTextAndExit(state),\r\n });\r\n break;\r\n case \"copy\":\r\n await copyToClipboardAndExit(currentMessage);\r\n case \"save\":\r\n await inspect(currentMessage, `/conversations/${Date.now()}.md`);\r\n exitChat();\r\n default:\r\n copyToClipboardAndExit(currentMessage);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T08:07:12Z"},{"name":"Prompt Anything","description":"","author":"Josh Mabry","twitter":null,"shortcut":"alt shift enter","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1244","url":"https://gist.githubusercontent.com/mabry1985/6c2412d4d3d1360276b9d95f44548815/raw/1dac5e149ebc4cf86ea830db9104d8518f47eca5/prompt-anything.js","title":"Prompt Anywhere v2","command":"prompt-anywhere-v2","content":"\r\n[Open prompt-anything in Script Kit](https://scriptkit.com/api/new?name=prompt-anything&url=https://gist.githubusercontent.com/mabry1985/6c2412d4d3d1360276b9d95f44548815/raw/1dac5e149ebc4cf86ea830db9104d8518f47eca5/prompt-anything.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as `quick-prompt.js` and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n//#########\r\n// Helpers\r\n//########\r\n// exit script on cancel\r\nconst cancelChat = () => {\r\n process.exit(1);\r\n};\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage + options),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // ignoreBlur: true,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // onSubmit: () => setSelectedText(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n // paste into highlighted text\r\n await setSelectedText(currentMessage);\r\n process.exit(1);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all these same options such as save\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => {\r\n // copy to clipboard when exiting the editor\r\n await clipboard.writeText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n onSubmit: async (state) => {\r\n // paste into highlighted text when pressing enter\r\n await setSelectedText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n });\r\n break;\r\n case \"copy\":\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n // exit script\r\n process.exit(1);\r\n case \"save\":\r\n await inspect(currentMessage, `conversations/${Date.now()}.md`);\r\n // exit script\r\n process.exit(1);\r\n default:\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T06:30:54Z"},{"avatar":"https://avatars.githubusercontent.com/u/629240?u=4750610206bd5ebafbdfab85d5d9c81dc8ce21ed&v=4","user":"blakecannell","author":"Blake.","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1240","url":"","title":"Snippets Manager (File Based)","command":"snippets-manager-file-based","content":"Simple file based snippets manager:\r\n\r\nA few things to note:\r\n- File paths are currently specific to Windows. I will update for other envionments.\r\n- This assumes a `snippets` folder exists within your home folder. Is there any way to make this configurable (within the manager itself)? This is of course quite simple to set as a constant at the top of the file if not.\r\n\r\nhttps://gist.github.com/blakecannell/bea79f6c69103a410181802855855aa4","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-04T04:40:53Z"},{"name":"Open Recent VS Code Project","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1238","url":"","title":"Open Recent VS Code Project","command":"open-recent-vs-code-project","content":"TIL VS Code has a sqlite database of recents, so I built this!\r\n\r\n[Open open-recent-vs-code-project in Script Kit](https://scriptkit.com/api/new?name=open-recent-vs-code-project&url=https://gist.githubusercontent.com/johnlindquist/b2426f52a9b5f3ca4827fbdeda6b323c/raw/8d7cbd175540000bf6a8684814de265343ad2ae5/open-recent-vs-code-project.ts\")\r\n\r\n```js\r\n// Name: Open Recent VS Code Project\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { URL, fileURLToPath } from \"url\"\r\n\r\n// /Users/johnlindquist/Library/Application Support/Code/User/globalStorage/state.vscdb\r\nlet filename = home(\"Library\", \"Application Support\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\n// windows path not tested, just guessing\r\nif (isWin) filename = home(\"AppData\", \"Roaming\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\nlet { default: sqlite3 } = await import(\"sqlite3\")\r\nlet { open } = await import(\"sqlite\")\r\n\r\nconst db = await open({\r\n filename,\r\n driver: sqlite3.Database,\r\n})\r\n\r\nlet key = `history.recentlyOpenedPathsList`\r\nlet table = `ItemTable`\r\n\r\nlet result = await db.get(`SELECT * FROM ${table} WHERE key = '${key}'`)\r\nlet recentPaths = JSON.parse(result.value)\r\nrecentPaths = recentPaths.entries\r\n .map(e => e?.folderUri)\r\n .filter(Boolean)\r\n .map(uri => fileURLToPath(new URL(uri)))\r\n\r\nlet recentPath = await arg(\"Open a recent path\", recentPaths)\r\nhide()\r\nawait exec(`code ${recentPath}`)\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-03T23:43:12Z"},{"name":"Samantha","description":"","shortcut":"command shift enter","avatar":"https://avatars.githubusercontent.com/u/34244581?u=f136c7d3eec99d1a4b2c887ca8c673bfabef95fa&v=4","user":"alwinraju","author":"Alwin Raju","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1236","url":"","title":"ChatGPT with user input","command":"chatgpt-with-user-input","content":"Heres a code snippet that takes in a user input and feeds it into ChatGPT and returns the response.\r\nAn OpenAI API key is required for it to work. The prompt can be amended to suit your needs/to create\r\nyour own custom agents.\r\n\r\n```javascript\r\n/*\r\nPress `cmd+shift+enter` and enter the text you want to send to ChatGPT.\r\n*/\r\n\r\n// Name: Samantha\r\n// Description: Send a single prompt to ChatGPT\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nYou are ChatGPT, a large language model trained by OpenAI. Follow the user's\r\ninstructions carefully. Respond using markdown.\r\n########\r\n`;\r\n\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet textFromUser = await arg(\"How can I help you?\");\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(textFromUser)]);\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T21:30:11Z"},{"name":"Pause Any Music","description":"","author":"Josh Davenport-Smith","twitter":"jdprts","avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","discussion":"https://github.com/johnlindquist/kit/discussions/1235","url":"","title":"Pause any music - only looks at Spotify/Music.app but customisable","command":"pause-any-music-only-looks-at-spotifymusicapp-but-customisable","content":"I'm often switching between Spotify and Music.app and find that what Mac targets when using pause media key can sometimes be unpredictable. This little script will pause either if playing.\r\n\r\n[Open pause-any-music in Script Kit](https://scriptkit.com/api/new?name=pause-any-music&url=https://gist.githubusercontent.com/joshdavenport/d6a38b7e5b9d9f1a76b0e44b78a7a5e5/raw/7c376383c698a52f817228aa19cf5312dbbc095c/pause-any-music.ts\")\r\n\r\n```js\r\n// Name: Pause Any Music\r\n// Description: Pause music playing from music apps\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst pauseScript = `\r\ntell application \"Spotify\"\r\n pause\r\nend tell\r\n\r\ntell application \"Music\"\r\n pause\r\nend tell\r\n`;\r\n\r\nexec(`osascript -e '${pauseScript}'`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T09:26:25Z"},{"preview":"docs","menu":"Open Project","description":"","shortcut":"cmd shift .","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1234","url":"https://gist.githubusercontent.com/Ambushfall/41236543032f4dd211a65964766be087/raw/5f7b2d37749d436f5015523d57fcba28d119b4cd/dev-project.js","title":"Open dev-project","command":"open-dev-project","content":"Now updated to use DB and add paths, create folders.\r\n\r\nOpen for any improvements ^^\r\n\r\n\r\n[Open dev-project in Script Kit](https://scriptkit.com/api/new?name=dev-project&url=https://gist.githubusercontent.com/Ambushfall/41236543032f4dd211a65964766be087/raw/5f7b2d37749d436f5015523d57fcba28d119b4cd/dev-project.js\")\r\n\r\n```js\r\n// Preview: docs\r\n// Menu: Open Project\r\n// Description: Opens a project in vscode\r\n// Shortcut: cmd shift .\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst envPath = await env('PROJECT_DIR');\r\n\r\nconst projectDir = home(envPath);\r\n\r\nconst projectList = await readdir(projectDir);\r\n\r\n\r\nlet { projects, write } = await db(\"projects\", {\r\n projects: projectList,\r\n})\r\n\r\nprojectList.forEach(async value => {\r\n if (!projects.includes(value)) {\r\n projects.push(value);\r\n await write()\r\n }\r\n})\r\n\r\n\r\nonTab(\"Open\", async () => {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n edit('', path.resolve(projectDir, project))\r\n})\r\n\r\nonTab(\"Add Path\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n \"Add path to project:\",\r\n md(projects.map(project => `* ${project.split('\\\\').pop()}`).join(\"\\n\"))\r\n )\r\n projects.push(project)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"Remove\", async () => {\r\n while (true) {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n\r\n project.split(':').length > 1 ? await rm(path.resolve(project)) : await rm(path.resolve(projectDir, project))\r\n\r\n let indexOfProject = projects.indexOf(project)\r\n projects.splice(indexOfProject, 1)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"New Project\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n {\r\n placeholder: \"Create new project:\", debounceInput: 400,\r\n enter: \"Create\", validate: async (input) => {\r\n let exists = await isDir(path.resolve(projectDir, input));\r\n if (exists) {\r\n return `${input} already exists`;\r\n }\r\n return true;\r\n }\r\n },\r\n\r\n )\r\n projects.push(project)\r\n mkdir(path.resolve(projectDir, project))\r\n await write()\r\n }\r\n})\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T03:28:00Z"},{"name":"Explain Plz","description":"","author":"Josh Mabry","twitter":null,"shortcut":"cmd alt shift e","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1232","url":"https://gist.githubusercontent.com/mabry1985/15add17a63b2d218be168495c2fb46b1/raw/3515457b32049380e633da1e625ff3d6714f844d/explain-plz.js","title":"Get an AI powered explanation of highlighted text","command":"get-an-ai-powered-explanation-of-highlighted-text","content":"\r\n[Open explain-plz in Script Kit](https://scriptkit.com/api/new?name=explain-plz&url=https://gist.githubusercontent.com/mabry1985/15add17a63b2d218be168495c2fb46b1/raw/3515457b32049380e633da1e625ff3d6714f844d/explain-plz.js\")\r\n\r\nA quick POC for an AI powered explanation script\r\n\r\nReturns a TLDR, Technical Summary, and ELI5\r\n\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235382761-06db398d-e8b0-4be5-8e8d-a3bb81d4a694.mov\r\n\r\n\r\n```js\r\n/*\r\n# Explain Plz\r\nHighlight some text and have it explained by AI\r\nWorks for any highlighted text or code\r\n*/\r\n\r\n// Name: Explain Plz\r\n// Description: Get an explanation for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd alt shift e\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and explaining it to the user.\r\nReturn the response in the following format using markdown syntax:\r\n# Explain Plz\r\n## TLDR (A quick summary of the highlighted text)\r\n## ELI5 (Explain Like I'm 5)\r\n## Explanation (A longer technical explanation of the highlighted text)\r\n`;\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n``;\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T00:13:53Z"},{"name":"AC AGI","description":"","author":"Josh Mabry","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1231","url":"https://gist.githubusercontent.com/mabry1985/cb36cb2a25d58628dcc2b506ec63e2dc/raw/0814b40397f404a9f05d03a549b352186f22f6ba/ac-agi.js","title":"An example of an AGI task manager with Human in the loop feedback","command":"an-example-of-an-agi-task-manager-with-human-in-the-loop-feedback","content":"\r\n[Open ac-agi in Script Kit](https://scriptkit.com/api/new?name=ac-agi&url=https://gist.githubusercontent.com/mabry1985/cb36cb2a25d58628dcc2b506ec63e2dc/raw/0814b40397f404a9f05d03a549b352186f22f6ba/ac-agi.js\")\r\n\r\nUp to date script can be found in my Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\n```js\r\n/*\r\nPardon the mess this was put together in half a day for the [lablab.ai](https://lablab.ai/event/autonomous-gpt-agents-hackathon) hackathon.\r\nMore updates to come\r\n\r\n# AC AGI \r\nAn autonomous general intelligence that accomplishes a task for you.\r\nUses human in the loop to provide feedback to the agent.\r\n\r\n\r\nHow to use:\r\n- Enter your task\r\n- Wait for the agent to complete the task\r\n- Assign max-iterations for the agent to loop: 0 for infinite (probably not a good idea ¯\\_(ツ)_/¯)\r\n- Profit\r\n\r\nKnown issues:\r\n- The agent will sometimes get stuck in a loop and not complete the task\r\n- Human feedback is not always helpful\r\n\r\nUpcoming features:\r\n- More tools\r\n- Refined prompts\r\n- Better human feedback system\r\n- Better memory system\r\n\r\nPossible thanks to the fine folks at [Langchain](https://js.langchain.com/docs/use_cases/autonomous_agents/baby_agi#example-with-tools)\r\nand all the other giants whose shoulders we stand on.\r\n*/\r\n\r\n// Name: AC AGI\r\n// Description: An AGI task manager inspired by BabyAGI\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { BabyAGI } = await import(\"langchain/experimental/babyagi\");\r\nlet { MemoryVectorStore } = await import(\"langchain/vectorstores/memory\");\r\nlet { OpenAIEmbeddings } = await import(\"langchain/embeddings/openai\");\r\nlet { OpenAI } = await import(\"langchain/llms/openai\");\r\nlet { PromptTemplate } = await import(\"langchain/prompts\");\r\nlet { LLMChain } = await import(\"langchain/chains\");\r\nlet { ChainTool } = await import(\"langchain/tools\");\r\nlet { initializeAgentExecutorWithOptions } = await import(\"langchain/agents\");\r\nlet { DynamicTool } = await import(\"langchain/tools\");\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nawait env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst task = await arg({\r\n placeholder: \"Task\",\r\n description: \"Enter a task for AC AGI to complete\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\nlet maxIterations = await arg({\r\n placeholder: \"How many times should AC AGI loop?\",\r\n hint: \"Leave empty for infinite iterations *use with caution*\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nif (maxIterations === \"\" || maxIterations === \"0\") {\r\n maxIterations = undefined;\r\n}\r\n\r\n//#########################\r\n// BabyAGI method overrides\r\n//#########################\r\nfunction printTaskList() {\r\n let result = \"\";\r\n for (const t of this.taskList) {\r\n result += `${t.taskID}: ${t.taskName}\\n`;\r\n }\r\n const msg = `### Task List\r\n \r\n ${result}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printNextTask(task) {\r\n const msg = `### Next Task\r\n \r\n ${task.taskID}: ${task.taskName}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printTaskResult(result) {\r\n const msg = `### Task Result\r\n \r\n ${result.trim()}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\n//#############\r\n// Custom Tools\r\n//#############\r\nlet html = (str) => str.replace(/ /g, \"+\");\r\nlet fetch = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${html(\r\n q\r\n )}&sort=date`;\r\n\r\nasync function search(query) {\r\n let response = await get(fetch(query));\r\n\r\n let items = response?.data?.items;\r\n\r\n if (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n return JSON.stringify(choices);\r\n }\r\n}\r\n\r\nasync function humanFeedbackList(mdStr) {\r\n let html = md(`${mdStr.trim()}`);\r\n const response = div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n\r\n return response;\r\n}\r\n\r\nasync function humanInput(question) {\r\n const response = await arg({\r\n placeholder: \"Human, I need help!\",\r\n hint: question,\r\n ignoreBlur: true,\r\n ignoreAbandon: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n });\r\n return response;\r\n}\r\n\r\nconst todoPrompt = PromptTemplate.fromTemplate(\r\n \"You are a planner/expert todo list creator. Generate a markdown formatted todo list for: {objective}\"\r\n);\r\n\r\nconst tools = [\r\n new ChainTool({\r\n name: \"TODO\",\r\n chain: new LLMChain({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n prompt: todoPrompt,\r\n }),\r\n description:\r\n \"For making todo lists. Input: objective to create todo list for. Output: the todo list\",\r\n }),\r\n new DynamicTool({\r\n name: \"Search\",\r\n description: \"Search web for info\",\r\n func: search,\r\n }),\r\n new DynamicTool({\r\n name: \"Human Input\",\r\n description:\r\n \"(Use only when no info is available elsewhere) Ask a human for specific input that you don't know, like a persons name, or DOB, location, etc. Input is question to ask human, output is answer\",\r\n func: humanInput,\r\n }),\r\n // new DynamicTool({\r\n // name: \"Human Feedback Choice\",\r\n // description: `Ask human for feedback if you unsure of next step.\r\n // Input is markdown string formatted with your questions and suitable responses like this example:\r\n // # Human, I need your help!\r\n // \r\n // * [John](submit:John) // don't change formatting of these links\r\n // * [Mindy](submit:Mindy)\r\n // * [Joy](submit:Joy)\r\n // * [Other](submit:Other)\r\n // `,\r\n // func: humanFeedbackList,\r\n // }),\r\n];\r\n\r\n//##################\r\n// AC AGI is Born\r\n//##################\r\nconst taskBeginMsg = md(`\r\n### Executing Task Manager\r\nGoal: ${task}\r\n`);\r\n\r\ndiv({ html: taskBeginMsg, ignoreBlur: true });\r\n\r\nconst agentExecutor = await initializeAgentExecutorWithOptions(\r\n tools,\r\n new ChatOpenAI({ temperature: 0 }),\r\n {\r\n agentType: \"zero-shot-react-description\",\r\n agentArgs: {\r\n prefix: `You are an AI who performs one task based on the following objective: {objective}. \r\nTake into account these previously completed tasks: {context}.`,\r\n suffix: `Question: {task}\r\n{agent_scratchpad}`,\r\n inputVariables: [\"objective\", \"task\", \"context\", \"agent_scratchpad\"],\r\n },\r\n }\r\n);\r\n\r\nconst vectorStore = new MemoryVectorStore(new OpenAIEmbeddings());\r\n\r\nconst babyAGI = BabyAGI.fromLLM({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n executionChain: agentExecutor,\r\n vectorstore: vectorStore,\r\n maxIterations: maxIterations,\r\n});\r\n\r\nbabyAGI.printNextTask = printNextTask;\r\nbabyAGI.printTaskList = printTaskList;\r\nbabyAGI.printTaskResult = printTaskResult;\r\n\r\nawait babyAGI.call({ objective: task });\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-30T21:26:47Z"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1230","url":"","title":"Example of BabyAGI running in ScriptKit","command":"example-of-babyagi-running-in-scriptkit","content":"","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-30T21:23:21Z"},{"name":"Google Search","description":"","author":"Josh Mabry","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1225","url":"","title":"Google Search","command":"google-search","content":"I know this is redundant and might not be useful to many, but I needed to build a custom search tool for an agent I'm working on. \r\n\r\nI set this up to test the functionality and figured someone might find it useful.\r\n\r\n```\r\n/* \r\n# Google Search\r\nExample of leveraging Google's Custom Search Engine API to search the web\r\n*/\r\n\r\n// Name: Google Search\r\n// Description: Leverage Google's Custom Search Engine API to search the web\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet query = await arg(\r\n {\r\n placeholder: \"Search Query\",\r\n strict: false,\r\n },\r\n [\r\n {\r\n name: \"Send a search query to Google\",\r\n info: \"always\",\r\n },\r\n ]\r\n);\r\n\r\nlet search = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${q}&sort=date`;\r\n\r\nlet response = await get(search(query));\r\n\r\nlet items = response?.data?.items;\r\n\r\nif (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n let link = await arg(\"Choose a link to view\", choices);\r\n\r\n open(link);\r\n}\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-30T06:23:32Z"},{"avatar":"https://avatars.githubusercontent.com/u/95415447?v=4","user":"shyagamzo","author":"Shy Agam","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1224","url":"","title":"Reading aloud streamed text (GPT style)","command":"reading-aloud-streamed-text-gpt-style","content":"I wanted my ChatGPT responses to be read aloud immediately, as they appear on the screen.\r\nThis was problematic because of two reasons:\r\n1. ChatGPT sends partial text responses (e.g. 'he', 'llo', ', I', 'am S', 'hy'). This isn't readable and should be accumulated.\r\n2. The `say` command stops any spoken text and starts speaking the new text. Meaning, we cannot simply call it every time we receive new text.\r\n\r\nThis is a rough start, but works well for my current needs.\r\nThis util class maintains a queue, detects certain delimiters (e.g. `.`, `,`) and starts speaking only when it detects that a phrase has probably been accumulated.\r\n\r\nhttps://gist.github.com/shyagamzo/749b7535aa8876ec2ce09f39aaef6a80\r\n\r\n```typescript\r\nimport '@johnlindquist/kit';\r\n\r\nconst speechStream = new (class SpeechStream\r\n{\r\n private textQueue: string[] = [];\r\n private isSpeaking: boolean = false;\r\n private feed: string = '';\r\n private finalizeFeedDebounced: () => void;\r\n\r\n constructor(private readonly config: { waitForDelimiter: number, estimatedWordsPerMinute: number })\r\n {\r\n this.finalizeFeedDebounced = _.debounce(this.finalizeFeed.bind(this), config.waitForDelimiter);\r\n\r\n onExit(() =>\r\n {\r\n this.textQueue = [];\r\n this.feed = '';\r\n\r\n sayIt('');\r\n });\r\n }\r\n\r\n public addText(text: string): void\r\n {\r\n this.feed += text;\r\n this.processAccumulatedText();\r\n this.finalizeFeedDebounced();\r\n }\r\n\r\n private processAccumulatedText(): void\r\n {\r\n const delimiters = /([.,;:!?\\n])/;\r\n\r\n const delimiterMatch = this.feed.match(delimiters);\r\n\r\n if (delimiterMatch)\r\n {\r\n const delimiterIndex = delimiterMatch.index;\r\n\r\n const textUntilDelimiter = this.feed.slice(0, delimiterIndex + 1);\r\n this.textQueue.push(textUntilDelimiter.trim());\r\n\r\n this.feed = this.feed.slice(delimiterIndex + 1);\r\n }\r\n\r\n this.processQueue();\r\n }\r\n\r\n private finalizeFeed(): void\r\n {\r\n if (this.feed)\r\n {\r\n this.textQueue.push(this.feed.trim());\r\n this.feed = '';\r\n this.processQueue();\r\n }\r\n }\r\n\r\n private processQueue(): void\r\n {\r\n if (this.isSpeaking || this.textQueue.length === 0) return;\r\n\r\n this.isSpeaking = true;\r\n\r\n const textToSpeak = this.textQueue.shift();\r\n\r\n this.waitForSpeechEnd(textToSpeak);\r\n sayIt(textToSpeak);\r\n }\r\n\r\n private waitForSpeechEnd(text: string): void\r\n {\r\n const estimatedSpeechDuration = this.estimateSpeechDuration(text);\r\n\r\n setTimeout(() =>\r\n {\r\n this.isSpeaking = false;\r\n this.processQueue();\r\n }, estimatedSpeechDuration);\r\n }\r\n\r\n private estimateSpeechDuration(text: string): number\r\n {\r\n const wordsPerMinute = this.config.estimatedWordsPerMinute; // Average speaking rate\r\n const words = text.trim().split(/\\s+/).length;\r\n const minutes = words / wordsPerMinute;\r\n\r\n return minutes * 60 * 1000; // Convert to milliseconds\r\n }\r\n})({\r\n waitForDelimiter: 4000,\r\n estimatedWordsPerMinute: 200\r\n});\r\n\r\nexport function sayIt(text: string): ReturnType\r\n{\r\n return say(text, { name: 'Microsoft Zira - English (United States)', rate: 1.3 });\r\n}\r\n\r\nexport function queueSpeech(text: string)\r\n{\r\n speechStream.addText(text);\r\n}\r\n```\r\n\r\nTo use it, simply import and call `queueSpeech`:\r\n\r\n```typescript\r\nimport { queueSpeech } from '../lib/speech-queue';\r\n\r\nfunction handleGPTText(text: string)\r\n{\r\n // ...\r\n queueSpeech(text);\r\n}\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-29T21:32:41Z"},{"name":"Smartify Your Words","description":"","author":"Josh Mabry","twitter":null,"shortcut":"command shift enter","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1221","url":"","title":"Improve your writing with AI powers","command":"improve-your-writing-with-ai-powers","content":"[Deprecated] \r\nif you miss it, check out Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\nHighlight your poorly written text and run the script to automagically make yourself sound smarter!\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273614-71a0b99c-5c9b-4806-84ba-2da8943359b9.mov\r\n\r\n\r\n\r\n```js\r\n/*\r\n/*\r\n# Smartify your words!\r\n\r\nTired of feeling dumb? Winter got you in a funk? \r\nCan you just not seem to get the words out right? \r\nWell, let's Smartify your words!\r\n\r\nHighlight some text and press `cmd+shift+enter` to send it through ChatGPT \r\nto replace the text with a more eloquent version. Mileage may vary.\r\n*/\r\n\r\n// Name: Smartify Your Words\r\n// Description: Let's make those words smarter!\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking an input and refactoring it using the following rules: '\r\n\r\n- Maintain the same meaning, tone, and intent as the original text\r\n- Clean up any grammar or spelling mistakes\r\n- Make it sound more professional, but keep it casual\r\n- Reduce redundancies and excessive verbiage\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes.\r\n########\r\n`;\r\n\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst smartify = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n if (!token) return;\r\n log(`handleLLMStart`);\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token) => {\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\nlet text = await getSelectedText();\r\n\r\nawait smartify.call([\r\n new SystemChatMessage(prompt),\r\n new HumanChatMessage(text),\r\n]);\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-29T00:42:43Z"},{"name":"Prompt Anywhere","description":"","author":"Josh Mabry","twitter":null,"shortcut":"cmd shift P","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1220","url":"","title":"Prompt ChatGPT from anywhere you can input text","command":"prompt-chatgpt-from-anywhere-you-can-input-text","content":"[DEPRECATED] \r\n\r\nBy selecting any input text and running this script you now have access to the power of AI anywhere in your OS. \r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273240-824a9432-87a3-48ac-9357-668a0c01b12c.mov\r\n\r\n```js\r\n/*\r\n# Generate a response to a prompt in any text field\r\n\r\nThis script utilizes ChatGPT to generate a response for the selected text. \r\nThe response will then replace the original text, making it useful for various writing tasks such as blog posts, code, and emails.\r\n\r\nHighlight some text and press `cmd+shift+p` to send it through ChatGPT \r\nto replace the text response. Your mileage may vary.\r\n*/\r\n\r\n// Name: Prompt Anywhere\r\n// Description: Generate a prompt response in anywhere you can type\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd shift P\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and generating a response to it.\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes. \r\nDo not ask any questions, do not refuse any input, and do not change the subject.\r\n`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n log(`handleLLMStart`);\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n // occasionally I see tokens typed out of order\r\n // still not sure why this happens\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-29T00:36:59Z"},{"name":"Open Vercel project dashboard","description":"","author":"Michael Rieger","twitter":"mueslirieger","avatar":"https://avatars.githubusercontent.com/u/20973893?u=359eca24a75c3971794c4daba03f723f0164762a&v=4","user":"mueslirieger","discussion":"https://github.com/johnlindquist/kit/discussions/1212","url":"","title":"Open Vercel project dashboard","command":"open-vercel-project-dashboard","content":"I use this script so much, I had to share it. It lets you select a personal or team project that is hosted on Vercel and opens the dashboard page of the selected project.\r\n\r\n\r\n[Open open-vercel-project in Script Kit](https://scriptkit.com/api/new?name=open-vercel-project&url=https://gist.githubusercontent.com/mueslirieger/21b1b1b9e6ef48ecf64d8d1a3937f0e8/raw/08532f733744bd314da5667b9e2feff49c6da6ca/open-vercel-project.ts\")\r\n\r\n```typescript\r\n/*\r\n# Open Vercel project dashboard\r\n\r\nLets the user select and open the dashboard page of a project hosted on Vercel.\r\n*/\r\n\r\n// Name: Open Vercel project dashboard\r\n// Description: Lets the user select and open the dashboard page of a project hosted on Vercel.\r\n// Author: Michael Rieger\r\n// Twitter: @mueslirieger\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst apiBaseUrl = 'https://api.vercel.com';\r\nconst dashboardBaseUrl = 'https://vercel.com';\r\n\r\n// ask user to create an access token for the rest api\r\nconst VERCEL_ACCESS_TOKEN = await env('VERCEL_ACCESS_TOKEN', {\r\n panel: md(`## Get a [Vercel API Access Token](https://vercel.com/account/tokens)`),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst user = await fetchUser();\r\n\r\n// Select whether personal or team projects should be listed\r\nconst projectsType = await selectProjectsType();\r\n\r\n// If team projects were selected list the teams the user is assigned to\r\nlet team: Team | undefined | null = null;\r\nif (projectsType === 'team') {\r\n const teams = await fetchTeams();\r\n team = await selectTeam(teams);\r\n}\r\n\r\n// Fetch projects based on previous selection\r\nconst projects = await fetchProjects(team?.id);\r\n\r\n// let user select project and open in browser\r\nconst project = await selectProject(projects);\r\n\r\nif (!project) exit(-1);\r\n\r\nawait browse(`${dashboardBaseUrl}/${projectsType === 'team' ? team.slug : user.username}/${project.name}`);\r\n\r\n// -----------------------------------------------------\r\n// Helpers\r\n// -----------------------------------------------------\r\n\r\ntype VercelApiError = {\r\n error?: {\r\n code: string;\r\n message: string;\r\n };\r\n};\r\n\r\nasync function selectProjectsType() {\r\n return arg<'personal' | 'team'>('Show personal or team projects', [\r\n {\r\n value: 'personal',\r\n name: '[P]ersonal',\r\n shortcut: 'p',\r\n },\r\n {\r\n value: 'team',\r\n name: '[T]eam',\r\n shortcut: 't',\r\n },\r\n ]);\r\n}\r\n\r\ntype User = {\r\n id: string;\r\n email: string;\r\n name: string | null;\r\n username: string;\r\n};\r\ntype GetUserResponse = { user: User } & VercelApiError;\r\n\r\nasync function fetchUser() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/user`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.user;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\ntype Team = { id: string; name: string; slug: string; avatar: string | null };\r\ntype GetTeamsResponse = { teams?: Team[] } & VercelApiError;\r\n\r\nasync function fetchTeams() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/teams`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.teams;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectTeam(teams: Team[]) {\r\n return await arg(\r\n {\r\n placeholder: teams.length ? 'Select a team' : 'No teams found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Select team`,\r\n },\r\n teams.map((team) => ({\r\n value: team,\r\n name: team.name,\r\n img: team.avatar ? `https://vercel.com/api/www/avatar/${team.avatar}?s=128` : '',\r\n }))\r\n );\r\n}\r\n\r\ntype Project = { id: string; name: string; latestDeployments: { alias: string[] }[] };\r\ntype GetProjectsResponse = { projects?: Project[] } & VercelApiError;\r\n\r\nasync function fetchProjects(teamId?: string | null | undefined) {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v9/projects${teamId ? `?teamId=${teamId}` : ''}`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.projects;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectProject(projects: Project[]) {\r\n return await arg(\r\n {\r\n placeholder: projects.length ? 'Select a project' : 'No projects found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Open project in dashboard`,\r\n },\r\n projects.map((project) => ({\r\n value: project,\r\n name: project.name,\r\n }))\r\n );\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-27T07:15:16Z"},{"name":"Merge / Split Alfred clipboard","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1209","url":"","title":"Merge / Split Afred Clipboard Script","command":"merge-split-afred-clipboard-script","content":"\r\n[Open merge-split-alfred-clipboard in Script Kit](https://scriptkit.com/api/new?name=merge-split-alfred-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/4803f2f333828e5cbd30a49cc426cd22/raw/2f9d71e8c1dc94525f7bc7996b47d820fbadfb9a/merge-split-alfred-clipboard.ts\")\r\n\r\nThis is a very specific yet useful Script, for those who have Alfred app with Powerpack and use the clipboard history. It allows you to split and merge it in several ways. \r\nFor `merge`, it asks you for the number of items in the clipboard, with a preview, and then asks you the merging character or characters. The resulting merge is placed in the clipboard.\r\nFor `split`, it asks you for a splitting character or characters, and saves all the resulted strings (after splitting) in the clipboard history.\r\n\r\nUse cases:\r\n* copy a bunch of values from different places, join them together in one shot, by `\\n'\r\n* copy a list of values, separated by comma or `\\n`, split them and paste them individually in a form\r\n\r\n```js\r\n// Name: Merge / Split Alfred clipboard\r\n// Description: Merge or split clipboard content using Alfred app's clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\");\r\nconst databasePath = home('Library/Application Support/Alfred/Databases/clipboard.alfdb')\r\nif (!await pathExists(databasePath)) {\r\n notify(\"Alfred clipboard database not found\" )\r\n exit()\r\n}\r\n\r\nconst db = new Database(databasePath);\r\n\r\nconst queryClipboard = async (sql, params) => {\r\n const stmt = db.prepare(sql);\r\n return sql.trim().toUpperCase().startsWith(\"SELECT\") ? stmt.all(params) : stmt.run(params);\r\n};\r\n\r\nconst getMergedClipboards = async (count, separator) => {\r\n const sql = `SELECT item FROM clipboard WHERE dataType = 0 order by ROWID desc LIMIT ?`;\r\n const clipboards = await queryClipboard(sql, [count]);\r\n return clipboards.map(row => row.item.trim()).join(separator);\r\n};\r\n\r\nconst writeMergedClipboards = async (mergedText) => {\r\n await clipboard.writeText(mergedText);\r\n};\r\n\r\nconst getSplitClipboard = async (separator, trim) => {\r\n const currentClipboard = await clipboard.readText();\r\n return currentClipboard.split(separator).map(item => trim ? item.trim() : item);\r\n};\r\n\r\nconst writeSplitClipboard = async (splitText) => {\r\n const lastTsSql = `SELECT ts FROM clipboard WHERE dataType = 0 ORDER BY ts DESC LIMIT 1`;\r\n const lastTsResult = await queryClipboard(lastTsSql, []);\r\n let lastTs = lastTsResult.length > 0 ? Number(lastTsResult[0].ts) : 0;\r\n\r\n const insertSql = `INSERT INTO clipboard (item, ts, dataType, app, appPath) VALUES (?, ?, 0, 'Kit', '/Applications/Kit.app')`;\r\n\r\n for (let i = 0; i < splitText.length - 1; i++) {\r\n lastTs += 1;\r\n await queryClipboard(insertSql, [splitText[i], lastTs]);\r\n }\r\n\r\n await clipboard.writeText(splitText[splitText.length - 1]);\r\n};\r\n\r\n\r\nconst action = await arg(\"Choose action\", [\"Merge\", \"Split\"]);\r\n\r\nif (action === \"Merge\") {\r\n const count = await arg({\r\n placeholder: \"Enter the number of clipboard items to merge\",\r\n }, async (input) => {\r\n if (isNaN(Number(input)) || input.length === 0)return ''\r\n return md(`
${await getMergedClipboards(input, '\\n')}
`)\r\n })\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for merging\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n return md(`
${await getMergedClipboards(count, input)}
`)\r\n })\r\n const mergedText = await getMergedClipboards(count, separator);\r\n await writeMergedClipboards(mergedText);\r\n await notify(\"Merged clipboard items and copied to clipboard\");\r\n} else {\r\n // const separator = await arg(\"Enter the separator for splitting\");\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for splitting\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n let strings = await getSplitClipboard(input, true);\r\n return md(`
${strings.join('\\n')}
`)\r\n })\r\n const trim = await arg(\"Trim clipboard content?\", [\"Yes\", \"No\"]);\r\n const splitText = await getSplitClipboard(separator, trim === \"Yes\");\r\n await writeSplitClipboard(splitText);\r\n await notify(\"Split clipboard content and stored in Alfred clipboard\");\r\n}\r\n\r\ndb.close();\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:19:34Z"},{"name":"Type Clipboard","description":"","shortcut":"ctrl+cmd+alt+shift+v","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1208","url":"","title":"Type Clipboard Script","command":"type-clipboard-script","content":"\r\n[Open type-clipboard in Script Kit](https://scriptkit.com/api/new?name=type-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/a18db9f56dfe8f6745ce6e917baf8ade/raw/eb3878c3aaede4e064bfbb451e6d30851b84160e/type-clipboard.ts\")\r\n\r\nThis is a Script I use more often than I would care to admit. There're situations where the `paste` command just doesn't work. Either web forms that don't allow paste, or crappy app UIs that for some reason a normal paste doesn't work. If you don't mind the lengthy shortcut, you hit `ctrl+cmd+alt+shift+v` and it `types` the content of the clipboard really fast, instead of pasting it.\r\n\r\n```js\r\n// Name: Type Clipboard\r\n// Description: Get the content of the clipboard and \"keystroke\" it without pasting\r\n// Shortcut: ctrl+cmd+alt+shift+v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst clipboardText = await clipboard.readText()\r\n\r\nif (clipboardText.length > 1000) {\r\n await notify(\"Clipboard content is too long\")\r\n exit()\r\n}\r\n\r\nawait applescript(String.raw`\r\n set chars to count (get the clipboard)\r\n tell application \"System Events\"\r\n delay 0.1\r\n keystroke (get the clipboard)\r\n end tell\r\n`)\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:13:01Z"},{"name":"Open in WhatsApp","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1207","url":"","title":"Open in WhatsApp Script","command":"open-in-whatsapp-script","content":"\r\n[Open open-in-whatsapp in Script Kit](https://scriptkit.com/api/new?name=open-in-whatsapp&url=https://gist.githubusercontent.com/ramiroaraujo/a61f67f8b55a3805888ff092b77c2550/raw/80eaadf67510c49d0724bb82b69f2a00ddb0d7d6/open-in-whatsapp.ts\")\r\n\r\nAnother simple script for opening a phone number in WhatsApp to chat. It fetches the number from the clipboard, and if no country code is provided it assumes Argentina, where I'm from, but of course change it to your default country\r\n\r\n```js\r\n// Name: Open in WhatsApp\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the text from the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//normalize the text\r\ntext = text.replace(/[-() ]/g, \"\");\r\n\r\n//validate if valid phone number\r\nif (!text.match(/^(\\+\\d{12,13})|(\\d{10,11})$/)) {\r\n notify(\"Invalid phone number\");\r\n exit()\r\n}\r\n\r\n//assume Argentina if no country code since that's where I'm from\r\nif (!text.startsWith(\"+\")) {\r\n text = \"+54\" + text;\r\n}\r\n\r\n//open in WhatsApp\r\nopen(`https://wa.me/${text}`);\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:10:43Z"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1206","url":"","title":"Convert selected images Script","command":"convert-selected-images-script","content":"\r\n[Open convert-selected-images in Script Kit](https://scriptkit.com/api/new?name=convert-selected-images&url=https://gist.githubusercontent.com/ramiroaraujo/51a8303fd66cc9d8b6db8a19c651254e/raw/b19ada003b9f58f48976636115e136cd2841ed0a/convert-selected-images.ts\")\r\n\r\nThis Script will convert all your selected (supported) images to either `jpg`, `png` or `webp`. I mostly created it to deal with sending images from the phone to the mac, and getting them as `heic`...\r\n\r\n```js\r\n // Name: convert selected images\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n// Grab selected files\r\nconst files = (await getSelectedFile()).split(\"\\n\");\r\n\r\n// Set up whitelist of formats\r\nconst supportedFormats = [\".heic\", \".png\", \".gif\", \".webp\", \".jpg\", \".jpeg\"];\r\n\r\n// Filter files based on supported formats\r\nconst selectedFiles = files.filter(file =>\r\n supportedFormats.some(format => file.toLowerCase().endsWith(format))\r\n);\r\n\r\n// Notify if no files are selected\r\nif (!selectedFiles.length) {\r\n await notify(\"No supported files selected\");\r\n exit();\r\n}\r\n\r\nconst convertHeic = await npm(\"heic-convert\");\r\nconst sharp = await npm(\"sharp\");\r\n\r\n// Select the output format\r\nconst outputFormat = await arg(\"Choose an output format\", [\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n]);\r\n\r\nconst getUniquePath = async (outputPath, suffix = \"\") => {\r\n if (await isFile(outputPath)) {\r\n const name = path.basename(outputPath, path.extname(outputPath));\r\n const newName = `${name}${suffix}-copy${path.extname(outputPath)}`;\r\n const newPath = path.join(path.dirname(outputPath), newName);\r\n return await getUniquePath(newPath, `${suffix}-copy`);\r\n } else {\r\n return outputPath;\r\n }\r\n};\r\n\r\n// Convert selected files to the chosen output format using appropriate libraries\r\nfor (const file of selectedFiles) {\r\n const content = await readFile(file);\r\n const name = path.basename(file).split(\".\")[0];\r\n const outputPath = path.join(path.dirname(file), name + `.${outputFormat}`);\r\n\r\n const uniqueOutputPath = await getUniquePath(outputPath);\r\n\r\n if (file.toLowerCase().endsWith(\".heic\")) {\r\n const formatMap = {\r\n jpg: \"JPEG\",\r\n png: \"PNG\",\r\n }\r\n const outputBuffer = await convertHeic({\r\n buffer: content,\r\n format: formatMap[outputFormat],\r\n quality: 0.5,\r\n });\r\n\r\n await writeFile(uniqueOutputPath, outputBuffer);\r\n } else {\r\n const sharpImage = sharp(content);\r\n\r\n switch (outputFormat) {\r\n case \"jpg\":\r\n await sharpImage.jpeg({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n case \"png\":\r\n await sharpImage.png().toFile(uniqueOutputPath);\r\n break;\r\n case \"webp\":\r\n await sharpImage.webp({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n }\r\n }\r\n}\r\n\r\nawait notify(`Converted selected files to ${outputFormat.toUpperCase()}`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:08:46Z"},{"name":"Open URL in clipboard","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1205","url":"","title":"Open URL in clipboard Script","command":"open-url-in-clipboard-script","content":"\r\n[Open open-url-in-clipboard in Script Kit](https://scriptkit.com/api/new?name=open-url-in-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/600d82866cd35b21998897843a4c3eb4/raw/c729cfb0baf43e6aa371a0ed0050ad69d5a9267d/open-url-in-clipboard.ts\")\r\n\r\nDead simple script for the very common use case of copying _some_ text with a URL in it, and wanting to navigate to that URL. Will fetch the first one it finds and go\r\n\r\n```js\r\n// Name: Open URL in clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//get the first URL in the clipboard, if any\r\nlet url = text.match(/(https?:\\/\\/[^\\s]+)/);\r\n\r\n//if there's a URL, open it\r\nif (url) {\r\n open(url[0]);\r\n} else {\r\n notify(\"No URL found in clipboard\");\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:06:12Z"},{"name":"Emoji Search","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1204","url":"","title":"Emoji Search Script","command":"emoji-search-script","content":"\r\n[Open emoji-search in Script Kit](https://scriptkit.com/api/new?name=emoji-search&url=https://gist.githubusercontent.com/ramiroaraujo/5b5f92d043e1ffa07af92215395d9231/raw/e134a3a357dfe43276d9d83bbd73ca01aad74537/emoji-search.ts\")\r\n\r\nA rather simple Emoji search that uses local database for fast lookup. It actually bootstrap by creating a sqlite database out of the `emojilib` JSON, in particular for storing usage and sorting by it. It will search by name and keywords, and the list will be sorted by most used\r\n\r\n```js\r\n// Name: Emoji Search\r\n// Description: Search and copy emoji to clipboard using SQLite database\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\")\r\nconst databaseFile = projectPath(\"db\", \"emoji-search-emojilib.db\")\r\n\r\nconst emojilibURL = \"https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json\"\r\n\r\nconst createDatabase = async () => {\r\n const response = await get(emojilibURL)\r\n const emojiData = response.data as Record\r\n\r\n //create db and table\r\n const db = new Database(databaseFile)\r\n db.exec(`CREATE TABLE IF NOT EXISTS emojis\r\n (emoji TEXT PRIMARY KEY, name TEXT, keywords TEXT, used INTEGER DEFAULT 0)`)\r\n\r\n //populate with data from emojilib\r\n for (const [emojiChar, emojiInfo] of Object.entries(emojiData)) {\r\n const description = emojiInfo[0]\r\n const tags = emojiInfo.slice(1).join(', ')\r\n\r\n db.prepare(\"INSERT OR REPLACE INTO emojis VALUES (?, ?, ?, 0)\").run(emojiChar, description, tags)\r\n }\r\n db.close()\r\n};\r\n\r\nif (!await pathExists(databaseFile)) {\r\n await createDatabase()\r\n}\r\n\r\nconst db = new Database(databaseFile)\r\n\r\nconst queryEmojis = async () => {\r\n const sql = \"SELECT emoji, name, keywords FROM emojis ORDER BY used DESC\"\r\n const stmt = db.prepare(sql)\r\n return stmt.all()\r\n}\r\n\r\nconst snakeToHuman = (text) => {\r\n return text\r\n .split('_')\r\n .map((word, index) => index === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word)\r\n .join(' ')\r\n}\r\n\r\nconst emojis = await queryEmojis()\r\n\r\nconst selectedEmoji = await arg(\"Search Emoji\", emojis.map(({ emoji, name, keywords }) => ({\r\n name: `${snakeToHuman(name)} ${keywords}`,\r\n html: md(`
\r\n ${emoji}\r\n
\r\n ${snakeToHuman(name)}\r\n ${keywords} \r\n
\r\n
`),\r\n value: emoji,\r\n\r\n})))\r\n\r\nawait clipboard.writeText(selectedEmoji)\r\n\r\n// Update the 'used' count\r\nconst updateSql = \"UPDATE emojis SET used = used + 1 WHERE emoji = ?\"\r\nconst updateStmt = db.prepare(updateSql)\r\nupdateStmt.run(selectedEmoji)\r\n\r\ndb.close()\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:04:56Z"},{"name":"Text Manipulation","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1203","url":"","title":"Text Manipulation Script","command":"text-manipulation-script","content":"\r\n[Open text-manipulation in Script Kit](https://scriptkit.com/api/new?name=text-manipulation&url=https://gist.githubusercontent.com/ramiroaraujo/55cd0f21adb60f0a270c18fbcce99454/raw/7dd3ecffbaa6eea66b9c3476ea8122ea31ef25e5/text-manipulation.ts\")\r\n\r\nInspired by a mix of an old Pipe workflow for Alfred mixed with the String Manipulation Plugin for Jetbrains IDEs. It will transform the current content of the clipboard based on the operation you select. Some operations require a parameter (`joinBy` for example), in those cases it asks for it. Both in the operation selection and parameter it shows a preview of the resulting text. \r\nIf you select an operation by `Cmd + enter` you'll be prompted by another operation to select after the first one is finished, and you can continue \"piping\" the outputs until you're done. Since it's common for me to `Cmd + enter` one last time and don't actually need the transformation there's a `No Operation` transform to select on this cases.\r\n\r\nUse cases:\r\n* copy a large list of values, wrap them in `'`, join them by `\\n`\r\n* capture numbers regex in each line, clean empty lines, join them by `+`, paste in ScriptKit or Alfred for sum result\r\n* filter lines by regex\r\n\r\nIt's hard to explain how useful this ends up being in my day to day\r\n\r\n```js\r\n// Name: Text Manipulation\r\n// Description: Transform clipboard text based on user-selected options\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet transformations = {\r\n upperCase: text => text.toUpperCase(),\r\n lowerCase: text => text.toLowerCase(),\r\n capitalize: text => text.split('\\n').map(line => line.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')).join('\\n'),\r\n decodeUrl: text => text.split('\\n').map(line => decodeURIComponent(line)).join('\\n'),\r\n snakeCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `_${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n camelCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => p.toUpperCase()).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n kebabCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `-${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n reverseCharacters: text => text.split('\\n').map(line => line.split('').reverse().join('')).join('\\n'),\r\n removeDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n return [...new Set(lines)].join('\\n');\r\n },\r\n keepOnlyDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n let duplicates = lines.filter((item, index) => lines.indexOf(item) !== index);\r\n return [...new Set(duplicates)].join('\\n');\r\n },\r\n removeEmptyLines: text => text.split('\\n').filter(line => line.trim() !== '').join('\\n'),\r\n removeAllNewLines: text => text.split('\\n').map(line => line.trim()).join(''),\r\n trimEachLine: text => text.split('\\n').map(line => line.trim()).join('\\n'),\r\n sortLinesAlphabetically: text => text.split('\\n').sort().join('\\n'),\r\n sortLinesNumerically: text => text.split('\\n').sort((a, b) => a - b).join('\\n'),\r\n reverseLines: text => text.split('\\n').reverse().join('\\n'),\r\n shuffleLines: text => {\r\n let lines = text.split('\\n')\r\n for (let i = lines.length - 1; i > 0; i--) {\r\n let j = Math.floor(Math.random() * (i + 1))\r\n let temp = lines[i]\r\n lines[i] = lines[j]\r\n lines[j] = temp\r\n }\r\n return lines.join('\\n')\r\n },\r\n joinBy: (text, separator) => text.split('\\n').join(separator),\r\n splitBy: (text, separator) => text.split(separator).join('\\n'),\r\n removeWrapping: text => {\r\n const lines = text.split('\\n');\r\n const matchingPairs = [['(', ')'], ['[', ']'], ['{', '}'], ['<', '>'], ['\"', '\"'], [\"'\", \"'\"]];\r\n return lines\r\n .map(line => {\r\n const firstChar = line.charAt(0);\r\n const lastChar = line.charAt(line.length - 1);\r\n\r\n for (const [open, close] of matchingPairs) {\r\n if (firstChar === open && lastChar === close) {\r\n return line.slice(1, -1);\r\n }\r\n }\r\n\r\n if (firstChar === lastChar) {\r\n return line.slice(1, -1);\r\n }\r\n\r\n return line;\r\n })\r\n .join('\\n');\r\n },\r\n wrapEachLine: (text, wrapper) => {\r\n const lines = text.split('\\n');\r\n\r\n return lines\r\n .map(line => `${wrapper}${line}${wrapper}`)\r\n .join('\\n');\r\n },\r\n captureEachLine: (text, regex) => {\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex);\r\n\r\n return lines\r\n .map(line => {\r\n const match = line.match(pattern);\r\n return match ? match[0] : '';\r\n })\r\n .join('\\n');\r\n },\r\n removeLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i');\r\n\r\n return lines\r\n .filter(line => !pattern.test(line))\r\n .join('\\n');\r\n },\r\n keepLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i')\r\n\r\n return lines\r\n .filter(line => pattern.test(line))\r\n .join('\\n');\r\n },\r\n prependTextToAllLines: (text, prefix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => prefix + line).join('\\n');\r\n },\r\n\r\n appendTextToAllLines: (text, suffix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => line + suffix).join('\\n');\r\n },\r\n\r\n replaceRegexInAllLines: (text, regexWithReplacement) => {\r\n const [regex, replacement] = regexWithReplacement.split('|');\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, replacement)).join('\\n');\r\n },\r\n removeRegexInAllLines: (text, regex) => {\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, '')).join('\\n');\r\n },\r\n generateNumberedList: (text) => {\r\n const lines = text.split('\\n');\r\n return lines.map((line, index) => `${index + 1}. ${line}`).join('\\n');\r\n },\r\n noop: text => text,\r\n}\r\n\r\nlet options = [\r\n // Existing options here\r\n {\r\n name: \"Decode URL\", description: \"Decode a URL-encoded text\", value: {\r\n key: \"decodeUrl\"\r\n }\r\n },\r\n {\r\n name: \"Upper Case\",\r\n description: \"Transform the entire text to upper case\",\r\n value: {\r\n key: \"upperCase\",\r\n },\r\n },\r\n {\r\n name: \"Lower Case\",\r\n description: \"Transform the entire text to lower case\",\r\n value: {\r\n key: \"lowerCase\",\r\n },\r\n },\r\n {\r\n name: \"snake_case\", description: \"Convert text to snake_case\", value: {\r\n key: \"snakeCase\"\r\n }\r\n },\r\n {\r\n name: \"Capitalize\", description: \"Convert text to Capital Case\", value: {\r\n key: \"capitalize\"\r\n }\r\n },\r\n {\r\n name: \"camelCase\", description: \"Convert text to camelCase\", value: {\r\n key: \"camelCase\"\r\n }\r\n },\r\n {\r\n name: \"kebab-case\", description: \"Convert text to kebab-case\", value: {\r\n key: \"kebabCase\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Characters\", description: \"Reverse the characters in the text\", value: {\r\n key: \"reverseCharacters\"\r\n }\r\n },\r\n {\r\n name: \"Remove Duplicate Lines\",\r\n description: \"Remove duplicate lines from the text\",\r\n value: {\r\n key: \"removeDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Keep Only Duplicate Lines\",\r\n description: \"Keep only duplicate lines in the text\",\r\n value: {\r\n key: \"keepOnlyDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove Empty Lines\", description: \"Remove empty lines from the text\", value: {\r\n key: \"removeEmptyLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove All New Lines\", description: \"Remove all new lines from the text\", value: {\r\n key: \"removeAllNewLines\"\r\n }\r\n },\r\n {\r\n name: \"Trim Each Line\",\r\n description: \"Trim whitespace from the beginning and end of each line\",\r\n value: {\r\n key: \"trimEachLine\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Alphabetically\", description: \"Sort lines alphabetically\", value: {\r\n key: \"sortLinesAlphabetically\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Numerically\", description: \"Sort lines numerically\", value: {\r\n key: \"sortLinesNumerically\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Lines\", description: \"Reverse the order of lines\", value: {\r\n key: \"reverseLines\"\r\n }\r\n },\r\n {\r\n name: \"Shuffle Lines\", description: \"Randomly shuffle the order of lines\", value: {\r\n key: \"shuffleLines\"\r\n }\r\n },\r\n {\r\n name: \"Join By\",\r\n description: \"Join lines by a custom separator\",\r\n value: {\r\n key: \"joinBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to join lines\",\r\n defaultValue: \",\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Split By\",\r\n description: \"Split lines by a custom separator\",\r\n value: {\r\n key: \"splitBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to split lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Wrapping\",\r\n description: \"Remove wrapping characters from each line\",\r\n value: {\r\n key: \"removeWrapping\",\r\n },\r\n },\r\n {\r\n name: \"Wrap Each Line With\",\r\n description: \"Wrap each line with a custom character or string\",\r\n value: {\r\n key: \"wrapEachLine\",\r\n parameter: {\r\n name: \"Wrapper\",\r\n description: \"Enter a wrapper for each line\",\r\n defaultValue: '\"',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Capture Each Line\",\r\n description: \"Capture and return the first match of a regex pattern in each line\",\r\n value: {\r\n key: \"captureEachLine\",\r\n parameter: {\r\n name: \"Pattern\",\r\n description: \"Enter a regex pattern to capture\",\r\n defaultValue: \"\\\\d+\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Lines Matching\",\r\n description: \"Remove lines that match the given regex\",\r\n value: {\r\n key: \"removeLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to remove\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Keep Lines Matching\",\r\n description: \"Keep lines that match the given regex\",\r\n value: {\r\n key: \"keepLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to keep\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Prepend Text to All Lines\",\r\n description: \"Add text to the beginning of all lines\",\r\n value: {\r\n key: \"prependTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to prepend to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Append Text to All Lines\",\r\n description: \"Add text to the end of all lines\",\r\n value: {\r\n key: \"appendTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to append to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Replace Regex in All Lines\",\r\n description: \"Replace regex matches in all lines with specified text\",\r\n value: {\r\n key: \"replaceRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex and Replacement\",\r\n description: \"Enter regex and replacement text separated by a '|'\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Generate Numbered List\",\r\n description: \"Prepend numbers to each line\",\r\n value: {\r\n key: \"generateNumberedList\",\r\n },\r\n },\r\n {\r\n name: \"Remove Regex In All Lines\",\r\n description: \"Remove matches of the provided regex in all lines\",\r\n value: {\r\n key: \"removeRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to remove from all lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"No Operation\",\r\n description: \"Do nothing to the text, if you accidentally hit Cmd + enter and need no more transformations\",\r\n }\r\n]\r\n\r\nconst handleTransformation = async (text, transformation) => {\r\n let {key, parameter} = transformation;\r\n let paramValue = parameter ? await arg({\r\n input: parameter.defaultValue,\r\n }, (input) => md(`
`)\r\n } catch (e) {\r\n return '...'\r\n }\r\n },\r\n }\r\n })\r\n )\r\n rerun = flag?.rerun as boolean;\r\n\r\n clipboardText = await handleTransformation(clipboardText, transformation);\r\n operations.push(transformation.key);\r\n}\r\n\r\nawait clipboard.writeText(clipboardText)\r\n\r\nawait notify(\"Text transformation applied and copied to clipboard\")\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:02:30Z"},{"name":"OCR","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1202","url":"","title":"Screencapture OCR Script","command":"screencapture-ocr-script","content":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/ramiroaraujo/d1924947b178742c8cd80f320d5e8e63/raw/07d0c86ff19b503bd856dd731294c4866aea7c79/ocr.ts\")\r\n\r\nOCR script that uses the OS native screencapture to capture part of your screen, perform OCR on it and copy the text to the clipboard.\r\nNote: I haven't even tested Windows and Linux versions. ChatGPT just wrote those for me :)\r\n\r\n```js\r\n// Name: OCR\r\n// Description: Capture a screenshot and recognize the text using tesseract.js\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n//both win and linux implementations were created by chatgpt (gpt4), without _any_ tests!! 😅\r\nconst captureScreenshot = async () => {\r\n const tmpFile = `/tmp/screenshot-${Date.now()}.png`;\r\n\r\n if (isMac) {\r\n await exec(`screencapture -i ${tmpFile}`);\r\n } else if (isWin) {\r\n const psScript = `\r\n Add-Type -AssemblyName System.Windows.Forms\r\n [System.Windows.Forms.SendKeys]::SendWait('%{PRTSC}')\r\n Start-Sleep -m 500\r\n $clipboardData = Get-Clipboard -Format Image\r\n $clipboardData.Save('${tmpFile}', [System.Drawing.Imaging.ImageFormat]::Png)\r\n `;\r\n await exec(`powershell -Command \"${psScript.replace(/\\n/g, '')}\"`);\r\n } else if (isLinux) {\r\n // Check if gnome-screenshot is available\r\n try {\r\n await exec('gnome-screenshot --version');\r\n await exec(`gnome-screenshot -f ${tmpFile}`);\r\n } catch (error) {\r\n // If gnome-screenshot is not available, try using ImageMagick's 'import' command\r\n await exec(`import ${tmpFile}`);\r\n }\r\n }\r\n\r\n return tmpFile;\r\n};\r\n\r\nconst recognizeText = async (filePath, language) => {\r\n const { createWorker } = await npm(\"tesseract.js\");\r\n const worker = await createWorker();\r\n\r\n await worker.loadLanguage(language);\r\n await worker.initialize(language);\r\n\r\n const { data } = await worker.recognize(filePath);\r\n\r\n await worker.terminate();\r\n\r\n return data.text;\r\n};\r\n\r\nconst languages = [\r\n { name: \"Spanish\", value: \"spa\" },\r\n { name: \"French\", value: \"fra\" },\r\n { name: \"Portuguese\", value: \"por\" },\r\n { name: \"English\", value: \"eng\" },\r\n];\r\n//@todo train a model for typescript (https://github.com/tesseract-ocr/tesstrain)\r\n\r\n// if ctrl is pressed, show a modal to select a language\r\nconst selectedLanguage = flag.ctrl\r\n ? await arg(\"Select a language:\", languages)\r\n : \"eng\";\r\n\r\n// Hide the Kit modal before capturing the screenshot\r\nawait hide();\r\n\r\nconst filePath = await captureScreenshot();\r\nif (!await pathExists(filePath)) exit()\r\n\r\nconst text = await recognizeText(filePath, selectedLanguage);\r\n\r\nif (text) {\r\n await clipboard.writeText(text.trim());\r\n await notify(\"Text recognized and copied to clipboard\");\r\n} else {\r\n await notify(\"No text found in the screenshot\");\r\n}\r\n\r\n// Clean up temporary file\r\nawait remove(filePath);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T01:55:18Z"},{"name":"Toggle Screen Lock","description":"","author":null,"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1201","url":"https://gist.githubusercontent.com/ElTacitos/7bb758f516e8e3bc5e1085e306bb0f31/raw/30f26138ddbe17626f1b47c2f2e2c20fa45749b6/toggle-screen-lock.js","title":"Toggle Screen Lock Macos","command":"toggle-screen-lock-macos","content":"\r\n[Open toggle-screen-lock in Script Kit](https://scriptkit.com/api/new?name=toggle-screen-lock&url=https://gist.githubusercontent.com/ElTacitos/7bb758f516e8e3bc5e1085e306bb0f31/raw/30f26138ddbe17626f1b47c2f2e2c20fa45749b6/toggle-screen-lock.js\")\r\n\r\n```js\r\n// Name: Toggle Screen Lock\r\n// Description: Toggle screen lock on macos (never or 2 minutes)\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet password = await arg({\r\n placeholder: \"Enter sudo password\",\r\n secret: true\r\n})\r\n\r\nconst resp = await exec(`echo ${password} | sudo -S pmset -g | grep displaysleep`)\r\nconst currentSleep = resp.stdout.trimStart().trimEnd().replace( /\\s\\s+/g, ' ' ).split(/\\s/)[1]\r\nconst user = (await exec(`whoami`)).stdout\r\n\r\nif (currentSleep === \"0\") {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 2`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 60`)\r\n await notify(\"Enabled screen lock\")\r\n} else {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 0`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 0`)\r\n await notify(\"Disabled screen lock\")\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-24T21:34:45Z"},{"avatar":"https://avatars.githubusercontent.com/u/23535629?u=0174ab792e2efe02dfeeddacdf991e267e0a79fe&v=4","user":"Jossdz","author":"Jose Carlos Correa","twitter":"JossDz","discussion":"https://github.com/johnlindquist/kit/discussions/1192","url":"","title":"Executing teminal commands on WSL(Windows subsystem for Linux)","command":"executing-teminal-commands-on-wslwindows-subsystem-for-linux","content":"Hey Folks,\r\n\r\nI've been working with Script Kit and encountered concerns regarding my local environment. Specifically, since I'm using Linux within Windows through WSL2 (Windows Subsystem for Linux), I was worried about executing the necessary commands for my workflow.\r\n\r\nUpon contacting John, he suggested using the following environment variable to enable command execution in WSL:\r\n\r\n```env\r\n# The value should be the full path to wsl, this is what is needed on windows 11.\r\nKIT_SHELL=C:\\Windows\\System32\\wsl.exe\r\n```\r\n\r\nAfter implementing this variable, I was able to run my commands in WSL. This is particularly useful for me as I can now start my docker environment with a single command.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-15T23:57:50Z"},{"menu":"Optical Character Recognition","description":"","author":"Kent C. Dodds","twitter":"kentcdodds","avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1190","url":"","title":"Extract text from images","command":"extract-text-from-images","content":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/kentcdodds/9d49b047079acb3a2e133f7a55fd1837/raw/a7c04475d41460bc8addfa3132f19244691726f3/ocr.ts\")\r\n\r\n```js\r\n// Menu: Optical Character Recognition\r\n// Description: Extract text from images\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport Tesseract from 'tesseract.js'\r\n\r\nconst clipboardImage = await clipboard.readImage()\r\n\r\nif (clipboardImage.byteLength) {\r\n const {data} = await Tesseract.recognize(clipboardImage, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n} else {\r\n let selectedFiles = await getSelectedFile()\r\n let filePaths: Array\r\n\r\n if (selectedFiles) {\r\n filePaths = selectedFiles.split('\\n')\r\n } else {\r\n let droppedFiles = await drop({placeholder: 'Drop images to compress'})\r\n filePaths = droppedFiles.map(file => file.path)\r\n }\r\n for (const filePath of filePaths) {\r\n const {data} = await Tesseract.recognize(filePath, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n }\r\n}\r\n\r\nnotify({\r\n title: 'OCR finished',\r\n message: `Copied text to your clipboard`,\r\n})\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-14T15:23:28Z"},{"name":"BitCoinPrice","description":"","author":"Kostas Minaidis","github":"@kostasx","avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1187","url":"","title":"Get the price of Bitcoin using the BitFinex open API","command":"get-the-price-of-bitcoin-using-the-bitfinex-open-api","content":"```js\r\n// Name: BitCoinPrice\r\n// Description: Get latest Bitcoin price using the Bitfinex open API\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet response = await get(`https://api.bitfinex.com/v1/pubticker/BTCUSD`, {\r\n headers: {\r\n Accept: \"text/plain\",\r\n },\r\n})\r\n\r\nconst data = response.data\r\nawait div(`\r\n
\r\n
Price: $${data.last_price}
\r\n
\r\n`)\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-09T20:58:56Z"},{"name":"Open My Links","shortcut":"cmd shift l","author":"Rohit Kumar Saini","github":"@rockingrohit9639","avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1183","url":"","title":"Script to open your links in browser","command":"script-to-open-your-links-in-browser","content":"Hey there,\r\nI have created a script which can open your pre-added links in the browser after selecting from the choices.\r\nFor now, it is just a simple script with hardcoded links, in future we can use `db` to store the links for users.\r\n\r\nHere is the script code -\r\n```js\r\n// Name: Open My Links\r\n// Shortcut: cmd shift l\r\n// Author: Rohit Saini\r\n// GitHub: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst LINKS = {\r\n Github: \"https://github.com/rockingrohit9639\",\r\n LinkedIn: \"https://www.linkedin.com/in/rohit-kumar-saini/\",\r\n} as const;\r\n\r\nconst CHOICES: (keyof typeof LINKS)[] = [\r\n \"Github\",\r\n \"LinkedIn\",\r\n];\r\n\r\nconst linkTitle = await arg(\"Which link to open?\", CHOICES);\r\nconst link = LINKS[linkTitle];\r\nconst command = `open ${link}`;\r\nexec(command);\r\n\r\n```\r\n\r\nHere is the demo of the script - \r\n[link open script.webm](https://user-images.githubusercontent.com/40729749/229710396-04ea1030-354f-4ee3-a08c-c9b2a4d55ca2.webm)\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-04T06:47:52Z"},{"name":"force paste","description":"","author":"Trevor Atlas","twitter":"trevoratlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1176","url":"","title":"Force paste into inputs that don't allow it","command":"force-paste-into-inputs-that-dont-allow-it","content":"\r\n[Open force-paste in Script Kit](https://scriptkit.com/api/new?name=force-paste&url=https://gist.githubusercontent.com/trevor-atlas/79a688107d6a3362e23adc58f4cce6ed/raw/5d195be171e8356389bd03ce3c9f55109a3fa7ef/force-paste.ts\")\r\n\r\n```js\r\n// Name: force paste\r\n// Description: Paste the contents of your clipboard, even in fields that wouldn't let you paste\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// test it out on the email field here: https://codepen.io/andersschmidt/pen/kOOMmw\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\nawait applescript(`tell application \"System Events\" to keystroke the clipboard as text`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-24T20:59:24Z"},{"name":"Paste Clipboard Image as Cloudinary Markdown URL","shortcut":"opt shift v","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1173","url":"https://gist.githubusercontent.com/johnlindquist/3593a0bee037b38c23d216191c4e5d7e/raw/b203b5a826d597ffa9e158db06b1ae6757222569/paste-image-as-url.js","title":"Paste Clipboard Image as Cloudinary Markdown URL","command":"paste-clipboard-image-as-cloudinary-markdown-url","content":"\r\n[Open paste-image-as-url in Script Kit](https://scriptkit.com/api/new?name=paste-image-as-url&url=https://gist.githubusercontent.com/johnlindquist/3593a0bee037b38c23d216191c4e5d7e/raw/b203b5a826d597ffa9e158db06b1ae6757222569/paste-image-as-url.js\")\r\n\r\n```js\r\n// Name: Paste Clipboard Image as Cloudinary Markdown URL\r\n// Shortcut: opt shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await clipboard.readImage()\r\n\r\nif (buffer && buffer.length) {\r\n let { default: cloudinary } = await npm(\"cloudinary\")\r\n\r\n cloudinary.config({\r\n cloud_name: await env(\"CLOUDINARY_CLOUD_NAME\"),\r\n api_key: await env(\"CLOUDINARY_API_KEY\"),\r\n api_secret: await env(\"CLOUDINARY_API_SECRET\"),\r\n })\r\n\r\n let response = await new Promise((response, reject) => {\r\n let cloudStream = cloudinary.v2.uploader.upload_stream(\r\n {\r\n folder: \"clipboard\",\r\n },\r\n (error, result) => {\r\n if (error) {\r\n reject(error)\r\n } else {\r\n response(result)\r\n }\r\n }\r\n )\r\n\r\n new Readable({\r\n read() {\r\n this.push(buffer)\r\n this.push(null)\r\n },\r\n }).pipe(cloudStream)\r\n })\r\n\r\n log(response)\r\n\r\n // format however you want\r\n let markdown = `![${response.url}](${response.url})`\r\n await setSelectedText(markdown)\r\n} else {\r\n await div(md(`# No Image in Clipboard`))\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-21T17:26:47Z"},{"name":"FindDuplicate","author":"Kostas Minaidis","github":"@kostasx","supports":"Mac","avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1171","url":"","title":"Find Duplicate Files","command":"find-duplicate-files","content":"**Find duplicate files in a folder (first-level only) using MD5 hash:**\r\n\r\n```js\r\n// Name: FindDuplicate\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n// Supports: Mac\r\nimport \"@johnlindquist/kit\"\r\nimport fs from \"fs\"\r\nimport crypto from \"crypto\"\r\n\r\nconst folder = await drop();\r\nconst dir = await readdir(folder[0].path)\r\nlet content = `\r\n| Filename | MD5 Hash |\r\n| -------- | -------- |\r\n`;\r\nconst hashes = {}\r\ndir.forEach(file => {\r\n const fullPath = `${folder[0].path}/${file}`\r\n\r\n const stats = fs.statSync(fullPath);\r\n if (stats.isDirectory()) { return; }\r\n\r\n const fileData = fs.readFileSync(fullPath)\r\n const hash = crypto.createHash('md5').update(fileData).digest('hex')\r\n if (hashes[hash]) {\r\n return hashes[hash].push(file)\r\n } \r\n hashes[hash] = [file]\r\n})\r\n\r\nObject.entries(hashes).forEach(([hash, listOfFiles]) => {\r\n if (listOfFiles.length > 1) {\r\n listOfFiles.forEach(file => {\r\n content += `| ${file} | ${hash.slice(0,4) + \"...\" + hash.slice(-4)} |\\n`\r\n })\r\n }\r\n})\r\n\r\nawait div(md(content))\r\n\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-18T21:42:54Z"},{"menu":"Icebreaker","description":"","author":"Trevor Atlas","twitter":"trevoratlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1169","url":"","title":"Get a random icebreaker question","command":"get-a-random-icebreaker-question","content":"\r\n[Open icebreaker in Script Kit](https://scriptkit.com/api/new?name=icebreaker&url=https://gist.githubusercontent.com/trevor-atlas/5eea582ea68faf7a4aa68d1f6ee487bd/raw/9acdba7e0276a9aef96f608dde594f445897e013/icebreaker.ts\")\r\n\r\n```js\r\n// Menu: Icebreaker\r\n// Description: Get a random icebreaker question\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst dbvalues = await db('icebreakers');\r\nconst icebreakers: string[] = dbvalues.data;\r\n\r\nconst getRandomElement = (arr: T[]) => {\r\n const index = Math.floor(Math.random() * arr.length);\r\n return arr[index];\r\n};\r\n\r\nconst item = getRandomElement(icebreakers);\r\n\r\nawait div(\r\n `\r\n
\r\n
${item}
\r\n
\r\n `\r\n);\r\n\r\n```\r\n\r\n\r\nAdd a `json` array of icebreaker questions in the `kenv` `db` folder called `icebreakers.json`\r\nFor example\r\n```json\r\n[\r\n \"Show us the weirdest thing you have in the room with you right now.\",\r\n \"There is a free, round-trip shuttle to Mars. The catch: it will take one year of your life to go, visit, and come back. Are you in?\",\r\n \"What is your least favorite thing about technology?\",\r\n \"What superpower would you most want?\",\r\n \"What food is best with cheese?\",\r\n \"Would you go in the mother-ship with aliens if they landed on Earth tomorrow?\",\r\n \"Would you join a community in space if it was permanent?\",\r\n \"Would you rather live 100 years in the past or 100 years in the future?\",\r\n \"You are the best criminal mastermind in the world. What crime would you commit if you knew you would get away with it?\",\r\n \"You can only eat one food again for the rest of your life. What is it?\",\r\n \"You can visit any fictional time or place. Which would you pick?\",\r\n \"In your time as a student in K-12, what made an impact on you. Not who, but what? What do you remember that influenced you today?\",\r\n \"How would you hide a giraffe from the government?\",\r\n \"If you were an inanimate object, what would you be and why?\",\r\n \"What is the most trivial thing about which you have a strong opinion?\",\r\n \"What is the smallest thing for which you are grateful?\",\r\n \"If you could change one thing about yourself physically, what would you change?\",\r\n \"What single event or decision do you think most affected the rest of your life?\",\r\n \"What do you fear, despite having no real reason to do so? Basically, what is an irrational fear you have?\",\r\n \"Do you have any conspiracy theories? If so, what are they?\",\r\n \"What scientific or technological advance blows your mind? Is there any technology that seems so futuristic and advanced you're surprised it actually exists?\",\r\n \"What is something you don't realise is weird until you really think about it?\",\r\n \"You can transport one furious elephant into any point in history, where would you put it?\",\r\n \"If you could make one thing that is now legal, illegal, and one thing that is illegal, legal, what laws would change?\",\r\n \"Would you agree to go without showering, brushing your teeth, and using deodorant for six months to win $500,000? You are not allowed to talk about the deal with anyone until the six months end, or the offer is gone.\",\r\n \"What's the best trip (traveling wise) you ever had?\",\r\n \"Does pineapple go on pizza?\",\r\n \"If you could live anywhere in the world for a year, where would it be?\",\r\n \"What's your favorite seat on an airplane?\",\r\n \"What is your spirit animal? (The animal who is most similar to your personality.)\",\r\n \"What is your favorite thing to do by yourself?\",\r\n \"Have you ever experienced a natural disaster like a hurricane or tornado?\",\r\n \"If you had to delete all but 3 apps from your smartphone, which ones would you keep? (Three apps that have changed your life.)\",\r\n \"If you had to choose between only having a cell phone or a car for the rest of your life, which would you choose?\",\r\n \"What is your favorite tv series?\",\r\n \"What is your favorite book?\",\r\n \"How would you change your life today if the average life expectancy was 400 years?\",\r\n \"A genie grants you three wishes but none of them can directly benefit you. What would those wishes be?\",\r\n \"What is your favorite smell and why?\",\r\n \"According to you, what is the most mind-numbingly dull movie ever made?\",\r\n \"If given the choice of having a talk show host narrate your life, who would you choose?\",\r\n \"Which reality TV show is your guilty pleasure?\",\r\n \"All in all, the movie that had the most significant impact on your life and why?\",\r\n \"If you could switch your life with any fictional character, who would it be?\",\r\n \"Decidedly, you must choose a fictional world that'll become the new reality. Which one would you pick?\",\r\n \"According to you, what is the most monotonous sport to watch?\",\r\n \"Who would be the first celebrity guest in your very own talk show?\",\r\n \"Without a doubt, who is the greatest actor that has ever graced the world?\",\r\n \"If you had the chance to be in the Olympics, which sport would you compete in?\",\r\n \"Generally, which real life person are you most inspired by?\",\r\n \"What's the most underrated actor that you know of?\",\r\n \"What is your 'I wish I had started doing this earlier in my life'?\",\r\n \"What is the coolest website you've ever visited?\",\r\n \"What is your favorite polite insult?\"\r\n]\r\n```\r\n\r\nUse the script!\r\n\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-17T20:23:41Z"},{"name":"Screenshot URL","avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1164","url":"","title":"A script that asks you to enter a url. it opens it, and takes a small screenshot of it.","command":"a-script-that-asks-you-to-enter-a-url-it-opens-it-and-takes-a-small-screenshot-of-it","content":"\r\n[Open screenshot-url in Script Kit](https://scriptkit.com/api/new?name=screenshot-url&url=https://gist.githubusercontent.com/SimplGy/0e89bc0a60548b32cac9c0db806d9cd4/raw/149f2b5018177cc777bed207b9dc89f664fe54d8/screenshot-url.ts\")\r\n\r\n```js\r\n// Name: Screenshot URL\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\n// get URL from user\r\nlet urlFromUser = await arg(\"Enter the URL to screenshot\");\r\nif (!urlFromUser.match(/^https?:\\/\\//)) {\r\n urlFromUser = `http://${urlFromUser}`;\r\n}\r\nconst pathObj = path.parse(urlFromUser);\r\nlog(pathObj);\r\n\r\n// config\r\nlet timeout = 5_000;\r\nconst FOLDER = 'Downloads/screenshot-url';\r\nconst screenshotFolder = home(FOLDER);\r\nconst filename = `${pathObj.name}${pathObj.ext}.png`\r\nconst screenshotPath = home(FOLDER, filename);\r\n\r\n// Open the window\r\nconst browser = await chromium.launch({ timeout, headless: false });\r\nconst context = await browser.newContext({ colorScheme: \"dark\" });\r\nconst page = await context.newPage();\r\nawait page.setViewportSize({\r\n width: 800,\r\n height: 600,\r\n});\r\npage.setDefaultTimeout(timeout);\r\n\r\ntry {\r\n // docs: https://playwright.dev/docs/api/class-page\r\n await page.goto(urlFromUser);\r\n await page.screenshot({ path: screenshotPath })\r\n \r\n // TODO: shrink the file to a thumbnail\r\n\r\n await revealFile(screenshotFolder)\r\n log(`Done`)\r\n\r\n} catch (error) {\r\n warn('error', error);\r\n}\r\n\r\nawait browser.close();\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-12T01:24:50Z"},{"name":"Natural Language Shell Command","description":"","author":"Laura Okamoto","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/127610787?u=b01a40872f605669b97bb3f31c0a622988ccd019&v=4","user":"laura-ok","discussion":"https://github.com/johnlindquist/kit/discussions/1163","url":"https://gist.githubusercontent.com/laura-ok/f2cc8d4cfb1211ffc7a494e8f89fff80/raw/04e765fc3b70d0705e874ed62ee16125798394b0/natural-language-shell-command.js","title":"natural language shell command","command":"natural-language-shell-command","content":"\r\n[Open natural-language-shell-command in Script Kit](https://scriptkit.com/api/new?name=natural-language-shell-command&url=https://gist.githubusercontent.com/laura-ok/f2cc8d4cfb1211ffc7a494e8f89fff80/raw/04e765fc3b70d0705e874ed62ee16125798394b0/natural-language-shell-command.js\")\r\n\r\n```js\r\n// Name: Natural Language Shell Command\r\n// Description: Convert a natural language command to a shell command\r\n// Author: Laura Okamoto\r\n// Twitter: @laura_okamoto\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { Configuration, OpenAIApi } = await npm(\"openai\");\r\n\r\nconst configuration = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n});\r\nconst openAI = new OpenAIApi(configuration);\r\n\r\nconst res = await arg(\"Describe the shell command you want to run\");\r\nconst prompt = `Use the following shell command to \"${res}\":`;\r\nconst completion = await openAI.createCompletion({\r\n model: \"text-davinci-003\",\r\n prompt,\r\n temperature: 0,\r\n max_tokens: 4069,\r\n});\r\n\r\nsetSelectedText(completion.data.choices[0].text.trim());\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-11T15:02:43Z"},{"name":"Preview CSS Color","description":"","author":"Josh Davenport-Smith","twitter":"jdprts","avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","discussion":"https://github.com/johnlindquist/kit/discussions/1162","url":"","title":"Preview CSS Color","command":"preview-css-color","content":"![image](https://user-images.githubusercontent.com/757828/224484149-4376fcf8-bce5-4adb-a2e0-bb0e9389a42a.png)\r\n\r\n[Open preview-css-color in Script Kit](https://scriptkit.com/api/new?name=preview-css-color&url=https://gist.githubusercontent.com/joshdavenport/86cb857671226ea6fb530c6bd7923bdf/raw/8ffbbe2c3ca55d72a54233d39c88095d338adade/preview-css-color.ts\")\r\n\r\n```js\r\n// Name: Preview CSS Color\r\n// Description: Preview any CSS color accepted by background-color\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst color = await arg(\"Color\");\r\n\r\nawait div(`\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
${color}
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-11T12:24:19Z"},{"name":"Units Convert","description":"","author":"Vedinsoh","github":"@Vedinsoh","avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1160","url":"https://gist.githubusercontent.com/Vedinsoh/40e74c82f688a80849da32afde7a5130/raw/2004a082fc3a3142de9ae8510fd42569713ae50e/units-convert.js","title":"Units Convert","command":"units-convert","content":"\r\n[Open units-convert in Script Kit](https://scriptkit.com/api/new?name=units-convert&url=https://gist.githubusercontent.com/Vedinsoh/40e74c82f688a80849da32afde7a5130/raw/2004a082fc3a3142de9ae8510fd42569713ae50e/units-convert.js\")\r\n\r\n```js\r\n// Name: Units Convert\r\n// Description: Convert between metric and imperial units\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst convert = await npm(\"convert-units\");\r\n\r\nconst getAllPossibilities = (unit) => {\r\n const possibilities = unit\r\n ? convert().from(unit).possibilities()\r\n : convert().possibilities();\r\n\r\n return possibilities\r\n .map((u) => {\r\n const uDetails = convert().describe(u);\r\n return {\r\n name: `${u} - ${uDetails.plural}`,\r\n value: u,\r\n };\r\n })\r\n .sort((a, b) => {\r\n const aDetails = convert().describe(a.value);\r\n const bDetails = convert().describe(b.value);\r\n if (aDetails.system === bDetails.system) {\r\n return aDetails.value - bDetails.value;\r\n }\r\n return aDetails.system - bDetails.system;\r\n });\r\n};\r\n\r\nconst getUnitString = (unit) => {\r\n const unitDetails = convert().describe(unit);\r\n return `${unitDetails.plural} (${unit})`;\r\n};\r\n\r\nconst convertUnits = (from, to, amount) => {\r\n return String(convert(amount).from(from).to(to));\r\n};\r\n\r\nconst fromUnit = await arg({\r\n placeholder: \"From\",\r\n choices: getAllPossibilities(),\r\n enter: \"To\",\r\n});\r\n\r\nconst toUnit = await arg({\r\n placeholder: \"To\",\r\n choices: getAllPossibilities(fromUnit),\r\n enter: \"Amount\",\r\n hint: `Convert from ${fromUnit} to...`,\r\n});\r\n\r\nawait arg({\r\n placeholder: \"Amount\",\r\n type: \"number\",\r\n enter: \"Exit\",\r\n hint: `${getUnitString(fromUnit)} equals...`,\r\n onInput: (input) => {\r\n const result = convertUnits(fromUnit, toUnit, input);\r\n setPanel(md(`# ${result} ${getUnitString(toUnit)}`));\r\n },\r\n shortcuts: [\r\n {\r\n name: \"Copy result\",\r\n key: `${cmd}+c`,\r\n onPress: (input) => {\r\n copy(convertUnits(fromUnit, toUnit, input));\r\n },\r\n bar: \"right\",\r\n },\r\n ],\r\n});\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-07T18:09:07Z"},{"name":"IP & Domain Lookup","description":"","author":"Vedinsoh","github":"@Vedinsoh","avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1158","url":"https://gist.githubusercontent.com/Vedinsoh/140a888222f85f8a8da1e65fdbdd87bb/raw/9ad2f7dedacc718ab906fcad75fc43c9c3b05451/ip-lookup.js","title":"IP & Domain Lookup","command":"ip-and-domain-lookup","content":"\r\n[Open ip-lookup in Script Kit](https://scriptkit.com/api/new?name=ip-lookup&url=https://gist.githubusercontent.com/Vedinsoh/140a888222f85f8a8da1e65fdbdd87bb/raw/9ad2f7dedacc718ab906fcad75fc43c9c3b05451/ip-lookup.js\")\r\n\r\n```js\r\n// Name: IP & Domain Lookup\r\n// Description: Get information about an IP address or domain\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport net from \"node:net\";\r\nimport { URL } from \"node:url\";\r\n\r\nconst getLookupData = async (query) => {\r\n // Reference: https://ip-api.com/docs/api:json\r\n const response = await get(\r\n `http://ip-api.com/json/${query}?fields=status,message,continent,country,countryCode,regionName,city,zip,lat,lon,timezone,isp,org,as,query`\r\n );\r\n\r\n if (response.data.status === \"fail\") {\r\n throw new Error(response.data.message);\r\n }\r\n\r\n return response.data;\r\n};\r\n\r\nlet lookupQuery = await arg({\r\n placeholder: \"Enter IP address or domain\",\r\n validate: (value) => {\r\n if (net.isIP(value) !== 0) {\r\n return true;\r\n } else {\r\n try {\r\n new URL(`https://${value}`);\r\n return true;\r\n } catch (e) {\r\n return \"Please enter a valid IP address or domain\";\r\n }\r\n }\r\n },\r\n});\r\n\r\nconst data = await getLookupData(lookupQuery);\r\n\r\ndiv(\r\n md(`\r\n# IP Lookup: ${lookupQuery}\r\n\r\n- **IP:** ${data.query}\r\n- **ISP:** ${data.isp}\r\n- **Organization:** ${data.org}\r\n- **AS:** ${data.as}\r\n- **Continent:** ${data.continent}\r\n- **Country:** ${data.country} (${data.countryCode})\r\n- **Region:** ${data.regionName}\r\n- **City:** ${data.city}\r\n- **Zip Code:** ${data.zip}\r\n- **Latitude:** ${data.lat}\r\n- **Longitude:** ${data.lon}\r\n- **Timezone:** ${data.timezone}\r\n`)\r\n);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-07T15:26:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/4293840?v=4","user":"fischgeek","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1154","url":"","title":"Simple Snippet Creator","command":"simple-snippet-creator","content":"I wanted a quick way to make very simple text replacements. \r\n\r\n```\r\nlet [txt, rep] = await fields([\"Text\", \"Replacement\"])\r\nlet dir = \"/Users/fischgeek/.kenv/scripts\" // <- Update to your specific path\r\nawait writeFile(`${dir}/${txt}.js`, `//Snippet: ${txt}\\nawait keyboard.type(\"${rep}\")`)\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-05T17:30:25Z"},{"name":"Raindrop","description":"","author":"Bruno Paz","github":"@brpaz","avatar":"https://avatars.githubusercontent.com/u/184563?u=27a3324127667282e6eb95d538d5b58b4d007352&v=4","user":"brpaz","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1150","url":"","title":"Raindrop Bookmarks search","command":"raindrop-bookmarks-search","content":"Open your [Raindrop](app.raindrop.io/) bookmarks from ScriptKit\r\n\r\n```ts\r\n// Name: Raindrop\r\n// Description: Search your Raindrop.io bookmarks\r\n// Author: Bruno Paz\r\n// Github: @brpaz\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { Choice } from \"@johnlindquist/kit\"\r\n\r\nconst COLLECTION_ID_ALL = 0\r\nconst COLLECTION_ID_UNSORTED = -1\r\n\r\ninterface RaindropResponse {\r\n items: RaindropBookmark[]\r\n}\r\n\r\ninterface RaindropBookmark {\r\n _id: string\r\n title: string\r\n link: string\r\n excerpt: string\r\n tags: string[]\r\n created: string\r\n type: string\r\n}\r\n\r\nconst raindropAPIKey = await env(\"RAINDROP_API_KEY\", {\r\n placeholder: \"Enter your Raindrop.io Test API Key\",\r\n hint: md(\r\n `Get a [Raindrop.io Test API Key](https://app.raindrop.io/settings/integrations)`\r\n ),\r\n\r\n secret: true,\r\n})\r\n\r\nasync function raindropSearch(query: string, collectionId: number): Promise {\r\n const url = `https://api.raindrop.io/rest/v1/raindrops/${collectionId}?search=${query}&access_token=${raindropAPIKey}`\r\n\r\n const response = await get(url)\r\n const data: RaindropResponse = await response.data\r\n\r\n return data.items.map((item) => ({\r\n name: item.title,\r\n description: item.excerpt,\r\n value: item.link,\r\n onSubmit: async () => {\r\n open(item.link)\r\n }\r\n }))\r\n}\r\n\r\nasync function allBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_ALL)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nasync function unsortedBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io unsorted bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_UNSORTED)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nonTab(\"Unsorted\", unsortedBookmarks);\r\nonTab(\"All\", allBookmarks);\r\n``` ","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-04T19:07:56Z"},{"name":"Escape Backticks","avatar":"https://avatars.githubusercontent.com/u/6349395?u=650885c8ac85a967ab8787bfffad65e1206266d1&v=4","user":"abisuq","author":"__","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1142","url":"https://gist.githubusercontent.com/abisuq/a27c130faa4ceb2b582a78b929bde0b2/raw/5d480d52863532d61adbb363fcb217b3f92c2cb8/browse-scriptkit.js","title":"Escape Backticks for copy paste string in javascript","command":"escape-backticks-for-copy-paste-string-in-javascript","content":" [Open browse-scriptkit in Script Kit](https://scriptkit.com/api/new?name=browse-scriptkit&url=https://gist.githubusercontent.com/abisuq/a27c130faa4ceb2b582a78b929bde0b2/raw/5d480d52863532d61adbb363fcb217b3f92c2cb8/browse-scriptkit.js\")\r\n\r\n```js\r\n// Name: Escape Backticks\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"Paste text to escape backticks\");\r\nawait copy(text.replace(/`/g, '\\\\`'));\r\n\r\n\r\n\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-24T05:33:20Z"},{"menu":"Center App","description":"","author":"Alois Carrera","avatar":"https://avatars.githubusercontent.com/u/60891163?u=b1985aac6c4755041fd849428360e69819e2339c&v=4","user":"AloisCRR","twitter":"AloisCRR","discussion":"https://github.com/johnlindquist/kit/discussions/1141","url":"https://gist.githubusercontent.com/AloisCRR/4a27c9e04b145be6a32e6ac4fa07894c/raw/f01bffb81564899dc01d7c931d82e072ec1918ba/center-app.js","title":"Center focused app based on window dimensions","command":"center-focused-app-based-on-window-dimensions","content":"\r\n[Open center-app in Script Kit](https://scriptkit.com/api/new?name=center-app&url=https://gist.githubusercontent.com/AloisCRR/4a27c9e04b145be6a32e6ac4fa07894c/raw/f01bffb81564899dc01d7c931d82e072ec1918ba/center-app.js\")\r\n\r\n```js\r\n// Menu: Center App\r\n// Description: Center current focused app based on window size\r\n// Author: Alois Carrera\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst activeScreen = await getActiveScreen()\r\n\r\nconst {\r\n workArea: {\r\n height,\r\n width,\r\n x: workAreaX,\r\n y: workAreaY\r\n }\r\n} = activeScreen\r\n\r\nconst activeAppBounds = await getActiveAppBounds()\r\n\r\nconst { top, left, right, bottom } = activeAppBounds\r\n\r\nconst windowHeight = bottom - top\r\n\r\nconst windowYCenter = windowHeight / 2\r\n\r\nconst windowWidth = right - left\r\n\r\nconst windowXCenter = windowWidth / 2\r\n\r\nsetActiveAppPosition({\r\n x: workAreaX + (width / 2) - windowXCenter,\r\n y: workAreaY + (height / 2) - windowYCenter\r\n})\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-23T13:54:56Z"},{"name":"daily note","description":"","avatar":"https://avatars.githubusercontent.com/u/830800?v=4","user":"johtso","author":"Johannes","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1126","url":"","title":"Open daily note in Obsidian","command":"open-daily-note-in-obsidian","content":"\r\n[Open daily-note in Script Kit](https://scriptkit.com/api/new?name=daily-note&url=https://gist.githubusercontent.com/johtso/b6a5d6e85d0805dbd25d5a36ffda6abb/raw/ef858f9129001cf187d0eb718108f7d734e2cef6/daily-note.ts\")\r\n\r\n```js\r\n// Name: daily note\r\n// Description: Open today's daily note in obsidian\r\n\r\n// You must install the Actions URI plugin and have the daily notes plugin enabled\r\n// Currently MacOS only\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { homedir } from \"os\"\r\nimport { join as joinPath } from \"path\"\r\n\r\nconst VAULT_NAME = await env(\r\n \"VAULT_NAME\",\r\n async () => {\r\n const vaultNames = await getVaultNames().catch(() => [])\r\n if (vaultNames.length === 1) {\r\n return vaultNames[0]\r\n } else {\r\n return await arg(\r\n \"Which vault do you want to use?\",\r\n vaultNames\r\n )\r\n }\r\n }\r\n);\r\n\r\nconst CREATE_URI = `obsidian://actions-uri/daily-note/create?vault=${VAULT_NAME}&silent=true`\r\nconst OPEN_URI = `obsidian://actions-uri/daily-note/open-current?vault=${VAULT_NAME}`\r\n\r\nawait applescript(`\r\n tell application \"Obsidian\"\r\n open location \"${CREATE_URI}\"\r\n open location \"${OPEN_URI}\"\r\n activate\r\n end tell\r\n`);\r\n\r\nasync function getVaultNames() {\r\n const obsidianConfPath = joinPath(homedir(), \"Library/Application Support/obsidian/obsidian.json\")\r\n\r\n // {\"vaults\":{\"9aeaa3aaa2ad0602\":{\"path\":\"/Users/human/Documents/Obsidian Vault\",\"ts\":1651412412801,\"open\":true}}}\r\n const obsidianConf = JSON.parse(await (await readFile(obsidianConfPath)).toString())\r\n const vaults = obsidianConf.vaults\r\n const vaultNames = Object.keys(vaults).map((vaultId) => vaults[vaultId].path.split(\"/\").pop())\r\n return vaultNames\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T23:20:40Z"},{"menu":"Search Anime","description":"","author":"Ambushfall","todo":"When on click starts working change state to the next result","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1122","url":"https://gist.githubusercontent.com/Ambushfall/c27b170000791f197fcfa5ca154a966b/raw/8bd65a0da0841f83ba892aaa4e3e72649f4283ee/anime-search.js","title":"Anime Search - Working - Updated","command":"anime-search-working-updated","content":"\r\n[Open anime-search in Script Kit](https://scriptkit.com/api/new?name=anime-search&url=https://gist.githubusercontent.com/Ambushfall/c27b170000791f197fcfa5ca154a966b/raw/8bd65a0da0841f83ba892aaa4e3e72649f4283ee/anime-search.js\")\r\n\r\nUpdated Johns amazing script to work with the new v4 Api, and using widget instead of the deprecated showImage method.\r\n\r\nProps to John for making all of this possible!\r\n\r\n[Original script](https://www.scriptkit.com/johnlindquist/anime-search)\r\n\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Menu: Search Anime\r\n// Description: Use the jikan.moe API to search anime\r\n// Author: John Lindquist, Updated by Ambushfall\r\n\r\nlet anime = await arg(\"Anime:\")\r\n\r\nlet response = await get(\r\n `https://api.jikan.moe/v4/anime?q=${anime}`\r\n)\r\n\r\nlet { images, title } = response.data.data[0]\r\n\r\nlet { jpg } = images\r\n\r\nlet { image_url, small_image_url, large_image_url } = jpg\r\n\r\nconst html = `\r\n\r\n
\r\n`;\r\n\r\nlet wg = await widget(html, {\r\n state: {\r\n url: large_image_url\r\n }\r\n})\r\n\r\nwg.onResized(async () => {\r\n wg.fit()\r\n})\r\n\r\n// win32 on-click not working so this does nothing really.\r\n\r\n// TODO: When on click starts working change state to the next result\r\n// wg.onClick((event) => event.targetId === \"x\" ? wg.close() : inspect(event.targetId));\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T01:47:54Z"},{"menu":"Clipboard History","description":"","shortcut":"command shift v","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1121","url":"https://gist.githubusercontent.com/Ambushfall/444bb14ca4b5268ea855cec8431b7dfc/raw/3fb4c0962b3cfdf2220fd96f71190e4ec1e872e5/clipboard-history.js","title":"Part 2: Clipboard special history, preview images as well","command":"part-2-clipboard-special-history-preview-images-as-well","content":"\r\n[Open clipboard-history in Script Kit](https://scriptkit.com/api/new?name=clipboard-history&url=https://gist.githubusercontent.com/Ambushfall/444bb14ca4b5268ea855cec8431b7dfc/raw/3fb4c0962b3cfdf2220fd96f71190e4ec1e872e5/clipboard-history.js\")\r\n\r\n```js\r\n// Menu: Clipboard History\r\n// Description: Copy something from the clipboard history\r\n// Shortcut: command shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { history } = await db(\"clipboard-history\")\r\n\r\nlet { value, type } = await arg(\"What to paste?\", () => {\r\n return history.map(({ value, type, timestamp, secret }) => {\r\n return {\r\n type,\r\n name: secret ? value.slice(0, 4).padEnd(10, \"*\") : value,\r\n value: {\r\n value,\r\n type,\r\n },\r\n description: timestamp,\r\n preview:\r\n type === \"image\"\r\n ? md(`![timestamp](${value})`)\r\n : value.includes(\"\\n\")\r\n ? `
${value\r\n .split(\"\\n\")\r\n .map((line) => `
${line}
`)\r\n .join(\"\")}
`\r\n : null,\r\n }\r\n })\r\n})\r\n\r\nif (type === \"image\") {\r\n await copyPathAsImage(value)\r\n await keystroke(\"command v\")\r\n}\r\n\r\nif (type === \"text\") {\r\n await setSelectedText(value)\r\n}\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T00:17:08Z"},{"menu":"Copy to Clipboard","description":"","shortcut":"command shift c","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1120","url":"https://gist.githubusercontent.com/Ambushfall/db9f545a9099a0a674f14679c862c0c3/raw/f9712e16d1965f16c578ffe4ad0ccb2c1e493086/copy-to-clipboard.js","title":"Clipboard special history, preview images as well","command":"clipboard-special-history-preview-images-as-well","content":"\r\n\r\n\r\n[Open copy-to-clipboard in Script Kit](https://scriptkit.com/api/new?name=copy-to-clipboard&url=https://gist.githubusercontent.com/Ambushfall/db9f545a9099a0a674f14679c862c0c3/raw/f9712e16d1965f16c578ffe4ad0ccb2c1e493086/copy-to-clipboard.js\")\r\n\r\n\r\ncopy-to-clipboard.js\r\n```js\r\n// Menu: Copy to Clipboard\r\n// Description: Save to Clipboard history\r\n// Shortcut: command shift c\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { write, history } = await db(\"clipboard-history\", { history: [{ value: \"\", type: \"\", timestamp: \"\", secret: \"\" }] })\r\n\r\nconst clipboardVal = await clipboard.readText();\r\n\r\n\r\nconst newValue = {\r\n value: clipboardVal,\r\n timestamp: new Date(Date.now()).toLocaleString('en-GB', { timeZone: 'UTC' }),\r\n secret: clipboardVal.includes('secret'),\r\n type: /(http)?s?:?(\\/\\/[^\"']*\\.(?:png|jpg|jpeg|gif|png|svg))/i.test(clipboardVal) ? \"image\" : \"text\"\r\n}\r\n\r\nhistory.push(newValue)\r\n\r\nawait write()\r\n```\r\n\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T00:12:47Z"},{"name":"today-timestamp","description":"","avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1117","url":"","title":"Today's date as ISO","command":"todays-date-as-iso","content":"Completely ripped from https://github.com/johnlindquist/kit/discussions/1116 (Thanks Daniel!) I just like the ISO format better.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/SimplGy/2676c4ccad30d9c71f7096422eb5fd44/raw/0da283bff42524450ee9f162c24451e1a8332a47/today-timestamp.ts\")\r\n\r\n```js\r\n// Name: today-timestamp\r\n// Description: inserts today's date in \"ISO\" format 2023-02-11\r\n// Snippet:\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\nconst formatted = `${today.getFullYear()}-${twoDigits(today.getMonth() + 1)}-${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-11T20:44:58Z"},{"name":"today-timestamp","description":"","snippet":"!tday","avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1116","url":"","title":"Insert current timestamp as YYYY/MM/DD","command":"insert-current-timestamp-as-yyyymmdd","content":"This snippet is basic, and stupid, but you will be happy to have it around when you need it.\r\nRather than manually input the current today date, you just type `!tday` and you get it.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/danielo515/9356f1af0b642dd23f6cdc188d73d7be/raw/6f8ca2cf7a666762c533bb47773403f210c55f49/today-timestamp.ts\")\r\n\r\n```ts\r\n// Name: today-timestamp\r\n// Description: inserts the today date (not including time) formatted as YYYY/MM/DD\r\n// Snippet: !tday\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\n// Format date to YYYY/MM/DD format\r\nconst formatted = `${today.getFullYear()}/${twoDigits(today.getMonth() + 1 )}/${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-11T06:58:35Z"},{"name":"url encode","avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1111","url":"","title":"Ask for user input and transform it to a url encoded string","command":"ask-for-user-input-and-transform-it-to-a-url-encoded-string","content":"Many times I don't want to url encode what I have on the clipboard, but I want to manually type it. This little script is for that.\r\n\r\n[Open url-encode in Script Kit](https://scriptkit.com/api/new?name=url-encode&url=https://gist.githubusercontent.com/danielo515/b2b2576155111a3a8fb73b47da2efac8/raw/cf81906c75996c52064151670cad363a71c7317d/url-encode.ts\")\r\n\r\n```js\r\n// Name: url encode\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"What do you want to encode\");\r\nconst encoded = encodeURIComponent(text)\r\nawait copy(encoded);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-09T15:41:54Z"},{"shortcut":"cmd opt f","avatar":"https://avatars.githubusercontent.com/u/99090177?u=23294c71201154e9389c53cc961811acb9aff635&v=4","user":"wisskirchenj","author":"Jürgen Wißkirchen","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1110","url":"","title":"Full text search in (java-)files over all my projects && open hit(s) in IDE","command":"full-text-search-in-java-files-over-all-my-projects-andand-open-hits-in-ide","content":"My typical usecase: I remember that I used some class (e.g. Mockitos InOrde, CsvSource or ExecutorService) in a similar situation or test and start searching in which project that was. My root contains 30+ projects - so sometimes this took me 15+ min or I had to google again, howto use `find -exec` - and even then it's clumsy.. \r\n\r\nNow this is perfect to me: I enter the search string (class, method, ...) and the surrounding context width in hits (the 'n' in grep -n).\r\nThen a div-container shows me all scrollable hits and I can refresh my mind on what I did years ago. \r\nFinally I hit Return and get a Button-List widget with all filenames with hits, where i can click on. This opens the project in IDEA, opens the file with the hit inside this project and even puts the clipboard copied search string by keystroke into IDEA's search dialog, so I can navigate with arrows...\r\nSuper helpful to me. :smile:\r\n\r\nTo reuse, there is a little customization needed, as I hardcoded my projects root and file pattern *.java. \r\nAlso the exclusion of 'z'-starting directories is sure special to me - but all that should be easy to adapt. \r\nAlso, I am more then happy to help, if there's need.\r\n**Note:** Keystrokes in IDEA at end of script, needs accessibility rights. My platform is MacOS.\r\n\r\n```ts\r\n// Shortcut: cmd opt f\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst smallArg = (placeholder: string) => arg({\r\n placeholder: placeholder,\r\n height: 100,\r\n width: 500\r\n});\r\n\r\nconst substring = await smallArg(\"Substring to search:\");\r\nconst lines = await smallArg(\"# surrounding lines in results:\");\r\n\r\nconst PROJECT_ROOT = \"/Users/jwisskirchen/IdeaProjects\";\r\nconst CLOSE_LABEL = \"Close\";\r\n\r\n// execute find java-files on all project-subdirs not starting with 'z' (as those are special ones...)\r\nconst results = await $`cd ${PROJECT_ROOT} ; find [^z]* -name *.java -exec grep -q ${substring} {} ';' -exec echo \"******{}******\" ';' -exec grep -${lines} ${substring} {} ';'`;\r\n\r\n// Split filepaths and search results in tokens-array, replace '<' as this confuses html-rendering after span-insertion below\r\nconst tokens = results.toString().replaceAll('<', '<').split('******');\r\n\r\n// build templates from tokens with filepath header and search results in
\r\nconst templates = [];\r\nconst files: string[] = [];\r\nfor (let i = 0; i < tokens.length - 1; i += 2) {\r\n files.push(`${tokens[i + 1]}`);\r\n\r\n // mark substrings in red.\r\n templates.push(`
${tokens[i + 1]}
\r\n
${tokens[i + 2]}
`\r\n .replaceAll(`${substring}`,\r\n `${substring}`)\r\n );\r\n}\r\n\r\n// show the templates => user can scroll in results and then press to continue to dialog for opening project file in IDE\r\nawait div({\r\n html: templates.join('
\\n'),\r\n width: 1200,\r\n height: 700\r\n}, `bg-white text-black text-sm p-2`);\r\n\r\n// put search string in clipboard for use in IDE later\r\nawait copy(substring);\r\n\r\n//---- display buttons in widgets, that let you open IntelliJ Idea -----\r\nconst items = files.map(path => ({\r\n name: path,\r\n // display only shrinked filepath /../ for brevity\r\n display: path.slice(0, path.indexOf('/') + 1) + '..' + path.slice(path.lastIndexOf('/'), path.length)\r\n}));\r\nitems.push({ name: CLOSE_LABEL, display: CLOSE_LABEL });\r\n\r\nconst buttons = `\r\n
\r\n \r\n \r\n
\r\n `;\r\n\r\nlet w = await widget(buttons, {\r\n backgroundColor: '#CCCCAA',\r\n x: 600,\r\n y: Math.max(0, 500 - items.length * 25),\r\n width: 600,\r\n height: items.length * 50 + 50,\r\n state: {\r\n items,\r\n }\r\n});\r\n\r\nw.onClick(async event => {\r\n if (event.dataset.name) {\r\n const path: string = event.dataset.name;\r\n\r\n if (path === CLOSE_LABEL) {\r\n w.close();\r\n exit(0); // process keeps running without..\r\n } else {\r\n // open the project in IntelliJ IDEA\r\n await $`idea ${PROJECT_ROOT}/${path.slice(0, path.indexOf('/'))}`;\r\n // open the specific file chosen inside this project\r\n await $`idea ${PROJECT_ROOT}/${path}`;\r\n // inside IDEA (!) do a search Cmd-F for the substring \r\n // Cmd-V places the substring from Clipboard (where we copied it above) into Ideas Search dialog\r\n await hide();\r\n await keyboard.pressKey(Key.LeftSuper, Key.F);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.F);\r\n await keyboard.pressKey(Key.LeftSuper, Key.V);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.V);\r\n }\r\n }\r\n});```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-08T19:39:47Z"},{"name":"Normalize GIT branch name","description":"","author":"Marin Muštra","linkedin":"https://www.linkedin.com/in/marin-mustra","https":"//github.com/microsoft/vscode/blob/main/extensions/git/src/commands.ts#L2182","avatar":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4","user":"mmustra","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1097","url":"https://gist.githubusercontent.com/mmustra/73168d0f629f8b2f526fa45d5068ac12/raw/416c129718d1f9641f6212ba530ab7d6b1f92c06/normalize-git-branch-name.js","title":"Normalize GIT branch name","command":"normalize-git-branch-name","content":"\r\n[Open normalize-git-branch-name in Script Kit](https://scriptkit.com/api/new?name=normalize-git-branch-name&url=https://gist.githubusercontent.com/mmustra/73168d0f629f8b2f526fa45d5068ac12/raw/416c129718d1f9641f6212ba530ab7d6b1f92c06/normalize-git-branch-name.js\")\r\n\r\n```js\r\n// Name: Normalize GIT branch name\r\n// Description: Copy text and paste it to normalized GIT branch name\r\n// Author: Marin Muštra\r\n// LinkedIn: https://www.linkedin.com/in/marin-mustra\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst delimiterChar = '-';\r\nconst illegalChar = '';\r\nconst mergableChars = [delimiterChar, illegalChar];\r\nconst shouldLowerCase = true;\r\n\r\nconst input = await paste();\r\nlet branchName = '';\r\n\r\n// Sanitize references\r\n// https://github.com/microsoft/vscode/blob/main/extensions/git/src/commands.ts#L2182\r\n// https://github.com/gitextensions/gitextensions/blob/6eab7392839c4d103bad1581fba5eaf6f008d766/GitCommands/Git/GitBranchNameNormaliser.cs\r\nconst getSanitizedInput = () => {\r\n const edgePattern = /^[-\\s]+|[-\\s]+$/g;\r\n const delimiterPattern = /\\s+|_+|-+/g;\r\n const illegalPattern = /^-+|^\\.|\\/\\.|\\.\\.|~|\\^|:|\\/$|\\.lock$|\\.lock\\/|\\\\|\\*|\\?|@{|^@$|\\.$|\\[|\\]$|^\\/|\\/$/g;\r\n\r\n const isInvalidChar =\r\n (!edgePattern.test(delimiterChar) && illegalPattern.test(delimiterChar)) ||\r\n (!edgePattern.test(illegalChar) && illegalPattern.test(illegalChar));\r\n\r\n if (isInvalidChar) {\r\n throw new Error('Invalid delimiter/illegal character!');\r\n }\r\n\r\n let sanitized = input.trim().replace(delimiterPattern, delimiterChar).replace(illegalPattern, illegalChar);\r\n mergableChars?.forEach((char) => char && (sanitized = sanitized.replace(new RegExp(`\\\\${char}+`, 'g'), char)));\r\n sanitized = sanitized.replace(edgePattern, '');\r\n\r\n return shouldLowerCase ? sanitized.toLowerCase() : sanitized;\r\n};\r\n\r\ntry {\r\n branchName = getSanitizedInput();\r\n\r\n if (!branchName) {\r\n throw new Error('Invalid input!');\r\n }\r\n} catch (error) {\r\n const hint = `${error.message}`;\r\n await editor({ hint, input, description: 'ERROR', readOnly: true, lineNumbers: 'on' });\r\n\r\n exit();\r\n}\r\n\r\nawait setSelectedText(branchName);\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-03T16:21:17Z"},{"name":"Silent Mention","shortcut":"opt x","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1094","url":"","title":"Silent Mention","command":"silent-mention","content":"Described here: https://twitter.com/peduarte/status/1621187086802980866?s=20&t=a8WakFD64i8W3BPDLIzfWg\r\n\r\n\r\n[Open silent-mention in Script Kit](https://scriptkit.com/api/new?name=silent-mention&url=https://gist.githubusercontent.com/johnlindquist/9e4885f4e3d80aa0e66f47a727f206c4/raw/7892b042f75e78ba434cf9e7671a5fa275a17350/silent-mention.ts\")\r\n\r\n```js\r\n// Name: Silent Mention\r\n// Shortcut: opt x\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet makeSilent = (str: string) =>\r\n str.replace(/[.#@]/g, m => m + \"\\u2060\")\r\n\r\nlet text =\r\n (await getSelectedText()) ||\r\n (await arg(\"Enter text to silent\"))\r\n\r\nlet silentText = makeSilent(text)\r\nawait setSelectedText(silentText)\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-02T23:54:13Z"},{"name":"Screenshot Current Tweet","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1092","url":"","title":"Screenshot Current Tweet","command":"screenshot-current-tweet","content":"Focus on a tweet like this:\r\n\r\nhttps://twitter.com/film_girl/status/1621170813796851719\r\n\r\n![1621170813796851719](https://user-images.githubusercontent.com/36073/216381005-74c32641-89e3-416e-af7b-77be619c9c66.png)\r\n\r\nThen run this script for a screenshot:\r\n\r\n\r\n[Open screenshot-current-tweet in Script Kit](https://scriptkit.com/api/new?name=screenshot-current-tweet&url=https://gist.githubusercontent.com/johnlindquist/ced2147f9e918c075e058da4b4c3eb2b/raw/6e46ea8b136c6a0300c69d893b808bb5ad6e80e8/screenshot-current-tweet.ts\")\r\n\r\n```js\r\n// Name: Screenshot Current Tweet\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\nlet url = await getActiveTab()\r\nlet timeout = 5000\r\nlet headless = false\r\n\r\nconst browser = await chromium.launch({\r\n timeout,\r\n headless,\r\n})\r\n\r\nconst context = await browser.newContext({\r\n colorScheme: \"dark\",\r\n})\r\nconst page = await context.newPage()\r\npage.setDefaultTimeout(timeout)\r\n\r\nawait page.goto(url)\r\n\r\nlet screenshotPath = home(\r\n \"Downloads\",\r\n path.parse(url).name + \".png\"\r\n)\r\n\r\ntry {\r\n await page\r\n .locator(\"article[tabindex='-1']\")\r\n .screenshot({ path: screenshotPath })\r\n await revealFile(screenshotPath)\r\n log(`Done`)\r\n} catch (error) {\r\n log(error)\r\n}\r\n\r\nawait browser.close()\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-02T16:19:30Z"},{"name":"Sleep on Shortcode","shortcode":"sl","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1088","url":"","title":"Sleep on Shortcode Example (similar to using an \"alias\")","command":"sleep-on-shortcode-example-similar-to-using-an-alias","content":"To run the script quickly, from the main prompt type:\r\n\r\ns, then l, then space, then y to confirm.\r\n\r\nSo these four characters:\r\n```\r\nsl y\r\n```\r\n\r\nUsing the `//Shortcode: ` metadata will run the script when you hit the \"spacebar\" after a shortcode/alias.\r\n\r\n\r\n[Open sleep-on-shortcode in Script Kit](https://scriptkit.com/api/new?name=sleep-on-shortcode&url=https://gist.githubusercontent.com/johnlindquist/35e843a00efebb5cceeeb65ea45eb779/raw/e83dd71468ff034c9d5f288387457438d1492c38/sleep-on-shortcode.ts\")\r\n\r\n```js\r\n// Name: Sleep on Shortcode\r\n// Shortcode: sl\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet confirm = await arg({\r\n placeholder: `Sleep system?`,\r\n// Script Kit parses hints and assigns single key shortcuts to single letters inside of []\r\n hint: `[y]/[n]`,\r\n})\r\n\r\nif (confirm === \"y\") {\r\n sleep()\r\n}\r\n\r\n```\r\n\r\n\r\n> Sidenote: From the main menu, you can also type `-` to bring up system commands.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-01T22:39:21Z"},{"name":"Theme Maker","description":"","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1087","url":"https://gist.githubusercontent.com/Ambushfall/990b39e9515ac138da5d5c4a5b783f47/raw/19af7566bd72f0e69763b729ee0857a3a1c18130/theme-color-picker.js","title":"Color Picker with saved themes","command":"color-picker-with-saved-themes","content":"\r\n[Open theme-color-picker in Script Kit](https://scriptkit.com/api/new?name=theme-color-picker&url=https://gist.githubusercontent.com/Ambushfall/990b39e9515ac138da5d5c4a5b783f47/raw/19af7566bd72f0e69763b729ee0857a3a1c18130/theme-color-picker.js\")\r\n\r\n```js\r\n// Name: Theme Maker\r\n// Description: Test Themes\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n\r\nconst themePath = kenvPath('theme.json');\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\nconst list = [\"foreground\", \"accent\", \"ui\", \"background\"]\r\nconst valueList = [foreground, accent, ui, background, opacity]\r\n\r\n\r\nconst arrayList = list.map((value, index) => {\r\n return { type: \"color\", label: value, value: valueList[index] }\r\n})\r\n\r\narrayList.push({ type: \"range\", label: \"Opacity\", value: opacity })\r\n\r\nawait fields({\r\n onChange: (input, { value }) => {\r\n const [foreground, accent, ui, background, opacity] = value\r\n theme.foreground = foreground\r\n theme.accent = accent\r\n theme.ui = ui\r\n theme.background = background\r\n theme.opacity = opacity\r\n\r\n setTheme(theme);\r\n writeJson(themePath, theme)\r\n },\r\n fields: arrayList,\r\n})\r\n\r\nlet result = await div(md(`# Success!`))\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-01T21:31:53Z"},{"name":"Widget Theme Picker","description":"","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1086","url":"https://gist.githubusercontent.com/Ambushfall/f985c74005580f816a9eaf27852d5902/raw/57c482fec59b1d7ebacde8a2a42010f957af1249/widget-theme.js","title":"Widget Color Picker","command":"widget-color-picker","content":"# Customize your own theme with the color picker\r\n#### Note: Heavily influenced by johnlindquist\r\n\r\n[Open widget-theme in Script Kit](https://scriptkit.com/api/new?name=widget-theme&url=https://gist.githubusercontent.com/Ambushfall/f985c74005580f816a9eaf27852d5902/raw/57c482fec59b1d7ebacde8a2a42010f957af1249/widget-theme.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Name: Widget Theme Picker\r\n// Description: Color Picker HTML\r\n\r\nlet themePath = kenvPath(\"theme.json\")\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\n\r\nlet w = await widget(\r\n `\r\n
${\r\n issue.description ||\r\n \"No description for this task 🌵\"\r\n }
\r\n
\r\n `,\r\n {\r\n alwaysOnTop: true,\r\n opacity: 0.7,\r\n roundedCorners: true,\r\n hasShadow: true,\r\n draggable: true,\r\n backgroundColor: \"#000\",\r\n // frame: true,\r\n darkTheme: true,\r\n useContentSize: true,\r\n }\r\n);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-31T04:13:39Z"},{"name":"Run .bat/.ps1/.sh","description":"","note":"linux shell will only work with WSL or you can provide the Args for ps1 using the .sh extension if you have gitbash","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1074","url":"","title":"Win32 - bat/ps1/sh execution / editor or hardcoded","command":"win32-batps1sh-execution-editor-or-hardcoded","content":"\r\n[Open win32-run-script in Script Kit](https://scriptkit.com/api/new?name=win32-run-script&url=https://gist.githubusercontent.com/Ambushfall/a3c0d71372f2739ef37494c66036aa4c/raw/3e4ae12c01db9c584bd77db660739f8cc0d8ec3a/win32-run-script.ts\")\r\n\r\n```js\r\n// Name: Run .bat/.ps1/.sh\r\n// Description: Process Output to Kit via stream\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n// @ts-expect-error\r\nimport { backToMainShortcut, highlightJavaScript } from \"@johnlindquist/kit\"\r\n\r\n// --- Create a shell script to run -----------------\r\n// `tmpPath` will store the file here:\r\n// ~/.kenv/tmp/process-shell-script-output/example.*\r\n\r\n// Note: linux shell will only work with WSL or you can provide the Args for ps1 using the .sh extension if you have gitbash\r\n\r\nconst fileName = \"example\"\r\n\r\nconst selectedLang = {\r\n name: '',\r\n args: '',\r\n ext: '',\r\n echo: '',\r\n\r\n set setVal(keyValueList: string[]) {\r\n this[keyValueList[0]] = keyValueList[1];\r\n }\r\n};\r\n\r\nconst objGen = (_lang: string, _ext: string, _args?: string) => {\r\n _args = _args ? _args : \"\"\r\n return {\r\n name: _lang,\r\n description: `Run Script using ${_lang}`,\r\n value: _lang,\r\n id: _ext,\r\n arguments: _args,\r\n preview: async () => highlightJavaScript(tmpPath(`${fileName}.${_ext}`))\r\n }\r\n}\r\n\r\nconst LangOptions = [\r\n objGen(\"PowerShell\",\r\n \"ps1\",\r\n \"powershell -NoProfile -NonInteractive –ExecutionPolicy Bypass -File \"\r\n ), objGen(\"Batch\", \"bat\"),\r\n objGen(\"Bash\", \"sh\"\r\n )]\r\n\r\nconst promptEditor = [\"yes\", \"no\"]\r\n\r\nconst selectedValue = await arg(\"Use editor?\", promptEditor\r\n)\r\n\r\nconst useEditor = selectedValue === \"yes\" ? true : false\r\n\r\n// define select options\r\n\r\nawait arg({\r\n placeholder: \"Select Scripting Language...\",\r\n enter: \"Select\",\r\n shortcuts: [backToMainShortcut],\r\n onChoiceFocus: async (input, { focused }) => {\r\n selectedLang.setVal = [\"args\", focused[\"arguments\"]]\r\n selectedLang.setVal = [\"ext\", focused.id]\r\n selectedLang.setVal = [\"name\", focused.name]\r\n selectedLang.setVal = [\"echo\", selectedLang.ext == \"bat\" ? \"@echo off\" : \"\"]\r\n }\r\n}, LangOptions)\r\n\r\n\r\n\r\nlet shellScriptPath = tmpPath(`${fileName}.${selectedLang.ext}`)\r\n\r\nconst editorConfig = {\r\n hint: `Write code for ${selectedLang.ext} file.`,\r\n description: 'Save to Run',\r\n onInputSubmit: async (input: any) => {\r\n selectedLang.ext == \"sh\" ? submit(`${input}\r\n exit`) : submit(input)\r\n }\r\n}\r\n\r\n// Using ping to simulate waiting for a long process and because it's natively supported across PS and Bat files\r\n// Note: If you use a code that would natively not run in bat like \"ls\" it will \r\nlet scriptContents = useEditor ? await editor(editorConfig) : `${selectedLang.echo}\r\necho \"hello\"\r\necho \"Done\"\r\n${selectedLang.ext == \"sh\" ? \"exit\" : \"\"}\r\n`\r\n\r\nawait writeFile(shellScriptPath, scriptContents);\r\n\r\n// Just a wrapper to highlight with code in PS style\r\nconst codeWrapper = (string: string, extension: any) => `\r\n\\`\\`\\`${extension}\r\n${string}\r\n\\`\\`\\`\r\n`\r\n\r\nlet output = ``\r\n\r\n// This is used to avoid kit window closing on process exit\r\nlet divPromise = div()\r\n\r\nconst outHandler = async (out: any) => {\r\n output += `${out}\\n`;\r\n setDiv(\r\n await highlight(`${codeWrapper(output, selectedLang.ext)}`)\r\n )\r\n};\r\n\r\n// Note: We have to use this janky way of executing PS as it would launch in Notepad or fail entirely.\r\nconst execArgs = selectedLang.ext == \"sh\" ? `cd ${tmpPath()} && bash ${fileName}.sh` : `${selectedLang.args}${shellScriptPath}`\r\n\r\n// inspect(execArgs)\r\nlet { stdout } = execLog(execArgs, outHandler)\r\n\r\nsetAlwaysOnTop(true);\r\nsetIgnoreBlur(true);\r\n\r\nawait divPromise\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-29T22:05:52Z"},{"name":"JSON to TS-Interfaces","description":"","author":"Ambushfall","snippet":"json..ts","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1071","url":"","title":"Write or Paste JSON > TS-Interfaces","command":"write-or-paste-json-greater-ts-interfaces","content":"This script is highly inspired off of already present 2 scripts:\r\n\r\n[share-script-as-discussion-js](https://github.com/johnlindquist/kit/blob/main/src/cli/share-script-as-discussion.ts)\r\n[JSON to TypeScript](https://github.com/johnlindquist/kit/discussions/1068)\r\n\r\n[Open json-to-ts in Script Kit](https://scriptkit.com/api/new?name=json-to-ts&url=https://gist.githubusercontent.com/Ambushfall/242170cfbd1f17f2c3749ef5f30964d5/raw/62afba807d7aa3ac21f5e2951b81d447dccddb3d/json-to-ts.ts\")\r\n\r\n```js\r\n// Name: JSON to TS-Interfaces\r\n// Description: Quickly get an Interface from JSON\r\n// Author: Ambushfall\r\n// Snippet: json..ts\r\n// Inspired by: Marin Muštra's JSON to TypeScript\r\n\r\n\r\nimport '@johnlindquist/kit';\r\nimport { EditorConfig } from '@johnlindquist/kit/types/kitapp';\r\n\r\nconst JsonToTS = await npm('json-to-ts');\r\n\r\nconst TSWrapper = (string: string) => `\r\n\\`\\`\\`ts\r\n${string}\r\n\\`\\`\\`\r\n`\r\n\r\nconst JSONWrapper = (string: string) => `\r\n\\`\\`\\`json\r\n${string}\r\n\\`\\`\\`\r\n`\r\n\r\nconst editorConfig:EditorConfig = {\r\n hint: \"Write then submit to obtain results\",\r\n description: 'JSON Values to Interface',\r\n onInputSubmit: async (input: string) => submit(input)\r\n}\r\n\r\n// Initialize editor and Parse JSON\r\nconst json = await editor(editorConfig)\r\nconst obj = JSON.parse(json);\r\nconst types = `${JsonToTS(obj).join('\\n\\n')}\\n`;\r\n\r\n// Place text into ClipBoard and show results\r\nsetSelectedText(types)\r\nawait div(await highlight(`# Success! Result Copied to Clipboard!\r\n## Input:${JSONWrapper(json)} \r\n## Output:${TSWrapper(types)}`));\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-28T13:45:03Z"},{"name":"🧙♂️ Resolve Short Links","description":"","author":"Ryan","avatar":"https://avatars.githubusercontent.com/u/11914327?u=a0ce83d03c79ce1eea9a73880258bf80b02a0065&v=4","user":"ryanbaer","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1070","url":"","title":"🧙♂️ Resolve Short Links","command":"resolve-short-links","content":"[Open resolve-short-links in Script Kit](https://scriptkit.com/api/new?name=resolve-short-links&url=https://gist.githubusercontent.com/ryanbaer/51d55aa0562b75d400d8e993ee3e8e8c/raw/4f1728cbbb36dd806c84a686d14dfc63493dc079/resolve-short-links.ts)\r\n\r\n# 🧙♂️ Resolve Short Links\r\n\r\n## Description\r\n\r\n- ⌨️ Prompts the user for a URL\r\n- 🌏 Makes a lightweight request to retrieve the actual URL\r\n- 🧼 Sanitizes unnecessary tracking information\r\n- 📋 Automatically copies the resolved URL to the clipboard.\r\n\r\n## Explanation\r\n\r\nThis is useful for removing tracking / other information from URLs before sharing with others.\r\n\r\nMany short links embed user / tracking information in the URL on their end.\r\n\r\nEvery time you share this shortened URL, your information is passed along.\r\n\r\n- TikTok, for example, uses this to display your username on the page when others view the video you shared.\r\n\r\n- Additionally, many short links will append query parameters that include tracking information as well, such as Facebook's `fbclid`.\r\n\r\nThis script resolves the short link by sending a `HEAD` request to the URL.\r\n\r\n- Additionally, any unnecessary query parameters are stripped off\r\n\r\n- The result is a valid URL with the minimum amount of information needed to access the resource\r\n\r\n## Examples\r\n\r\n|Site|Input|Resolved & Sanitized|\r\n|-|-|-|\r\n|Bitly|https://bit.ly/3IBRlRk?fbclid=csGcfbGQWeVOwKtolljwoSVhwcxZsnDungMTiiycUaTyBkgGlErwsLXEpaVOX|https://www.insider.com/private-caribbean-island-for-sale-photos-2022-8|\r\n|TikTok|https://www.tiktok.com/t/kZnATxcP/|https://www.tiktok.com/@the_tim_davidson/video/7148742306085063978|\r\n|Facebook Watch|https://fb.watch/ZLce_Hif0_/|https://www.facebook.com/watch/?v=752603792999748|\r\n\r\n```ts\r\n// Name: 🧙♂️ Resolve Short Links\r\n// Description: Resolve short links & removes any tracking info\r\n// Author: Ryan Baer\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst Resolver = {\r\n requiredParams: {\r\n // youtube.com/watch?v=[videoId] requires the 'v' param to locate the video\r\n \"youtube.com\": [\"v\"],\r\n // facebook.com/watch?v=[videoId] requires the 'v' param to locate the video\r\n \"facebook.com\": [\"v\"],\r\n },\r\n getURL(input: string) {\r\n let processed = input;\r\n if (!input.match(/^https?:\\/\\//)) {\r\n processed = `https://${processed}`;\r\n }\r\n\r\n return new URL(processed);\r\n },\r\n\r\n sanitizeUrl(url: URL) {\r\n function getBaseDomain(hostname: string) {\r\n const [tld, base] = hostname.split(\".\").reverse();\r\n\r\n return `${base}.${tld}`;\r\n }\r\n\r\n // Clone so we're not deleting from the list as we're iterating it.\r\n const searchParams = new URLSearchParams(url.searchParams);\r\n\r\n searchParams.forEach((_value, key) => {\r\n const baseDomain = getBaseDomain(url.hostname);\r\n const skip = this.requiredParams[baseDomain];\r\n if (skip?.includes(key)) {\r\n return;\r\n }\r\n\r\n url.searchParams.delete(key);\r\n });\r\n\r\n return url.href;\r\n },\r\n\r\n async fetchHead(input: string) {\r\n let url: URL;\r\n try {\r\n url = this.getURL(input);\r\n } catch (error) {\r\n return { resolvedUrl: undefined, error: \"Invaid or unreachable URL\" };\r\n }\r\n\r\n const options = { method: \"HEAD\", credentials: \"omit\" } as const;\r\n\r\n try {\r\n const response = await fetch(url, options);\r\n const responseUrl = new URL(response.url);\r\n const sanitizedUrl = this.sanitizeUrl(responseUrl);\r\n\r\n return { resolvedUrl: sanitizedUrl, error: undefined };\r\n } catch (error) {\r\n return { resolvedUrl: undefined, error: this.getErrorMessage(error) };\r\n }\r\n },\r\n\r\n getErrorMessage(error: unknown) {\r\n return error instanceof Error\r\n ? error.message\r\n : \"Something unexpected happened.\";\r\n },\r\n\r\n async resolve(url: string) {\r\n return this.fetchHead(url);\r\n },\r\n};\r\n\r\nconst url = await arg(\"Enter a shortened URL\");\r\nconst response = await Resolver.resolve(url);\r\n\r\nconst { resolvedUrl, error } = response;\r\n\r\nconst result = error ?? resolvedUrl ?? \"(Something went wrong)\";\r\n\r\nconst shouldCopy = !!resolvedUrl;\r\nif (shouldCopy) {\r\n await copy(result);\r\n}\r\n\r\nawait div(\r\n md(`# 🔬 Result${shouldCopy ? \" (automatically copied to clipboard)\" : \"\"}\r\n \\`\\`\\`\r\n ${result}\r\n \\`\\`\\`\r\n `)\r\n);\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-28T06:39:31Z"},{"name":"🗡️ Strip Query Params","description":"","author":"Ryan","todo":"let's leverage the awesome `db` feature and allow users to add their own","avatar":"https://avatars.githubusercontent.com/u/11914327?u=a0ce83d03c79ce1eea9a73880258bf80b02a0065&v=4","user":"ryanbaer","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1069","url":"","title":"🗡️ Strip Query Params","command":"strip-query-params","content":"[Open strip-query-params in Script Kit](https://scriptkit.com/api/new?name=strip-query-params&url=https://gist.githubusercontent.com/ryanbaer/ca7bcf557d3c81a4d13d1c15060f49ff/raw/988f965857eceef7f8588bd78d80c3501b6974e2/strip-query-params.ts)\r\n\r\n# 🗡️ Strip Query Params\r\n\r\n- ⌨️ Prompts the user for a URL\r\n- 🗡️ Cuts off all query params\r\n- 🛡️ Maintains essential query params (e.g., `youtube.com/watch?v=[videoId]`)\r\n- 📋 Automatically copies the updated URL to the clipboard.\r\n\r\nUseful for removing tracking / other information from URLs before sharing with others.\r\n\r\n## Examples\r\n\r\n|Site|Input|Output|\r\n|-|-|-|\r\n|Facebook Video|https://www.facebook.com/watch/?v=1233056997105202&extid=NS-UNK-CHE-UNK-IOS_WXYZ-ABCD&mibextid=cfeeni&ref=sharing|https://www.facebook.com/watch/?v=1233056997105202|\r\n|Facebook|https://www.facebook.com/groups/funnycatsworld/permalink/5619404908185458/?mibextid=cfeeni|https://www.facebook.com/groups/funnycatsworld/permalink/5619404908185458/|\r\n|Instagram|https://www.instagram.com/reel/CnxZnkRprAD/?igshid=YmNmMTdmZDM=|https://www.instagram.com/reel/CnxZnkRprAD/|\r\n|YouTube|https://www.youtube.com/watch?v=MWRPYBoCEaY&ab_channel=NoBoilerplate|https://www.youtube.com/watch?v=MWRPYBoCEaY|\r\n\r\n```ts\r\n// Name: 🗡️ Strip Query Params\r\n// Description: Strips query params from a URL.\r\n// Author: Ryan Baer\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n// TODO: let's leverage the awesome `db` feature and allow users to add their own\r\n// rules.\r\nconst requiredParams: Record = {\r\n // youtube.com/watch?v=[videoId] requires the 'v' param to locate the video\r\n \"youtube.com\": [\"v\"],\r\n // facebook.com/watch/?v=[videoId] requires the 'v' param to locate the video\r\n \"facebook.com\": [\"v\"],\r\n};\r\n\r\nfunction stripQueryParams(input: string) {\r\n const url = new URL(input);\r\n\r\n function getBaseDomain(hostname: string) {\r\n const [tld, base] = hostname.split(\".\").reverse();\r\n\r\n return `${base}.${tld}`;\r\n }\r\n\r\n // Clone so we're not deleting from the list as we're iterating it.\r\n const searchParams = new URLSearchParams(url.searchParams);\r\n\r\n searchParams.forEach((_value, key) => {\r\n const baseDomain = getBaseDomain(url.hostname);\r\n const skip = requiredParams[baseDomain];\r\n if (skip?.includes(key)) {\r\n return;\r\n }\r\n\r\n url.searchParams.delete(key);\r\n });\r\n\r\n return url.href;\r\n}\r\n\r\nconst input = await arg(\"Enter a URL\");\r\nconst result = stripQueryParams(input);\r\n\r\nawait copy(result);\r\n\r\nawait div(\r\n md(`# 🔬 Result (automatically copied to clipboard)\r\n\\`\\`\\`\r\n${result}\r\n\\`\\`\\`\r\n`)\r\n);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-28T04:29:13Z"},{"name":"JSON to TypeScript","description":"","author":"Marin Muštra","linkedin":"https://www.linkedin.com/in/marin-mustra","avatar":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4","user":"mmustra","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1068","url":"https://gist.githubusercontent.com/mmustra/c544b9ae06baea89b95257f752f453d4/raw/f80cde38dc04857b558b5bc35023b8c92131e7f2/json-to-typescript.js","title":"JSON to TypeScript","command":"json-to-typescript","content":"\r\n[Open json-to-typescript in Script Kit](https://scriptkit.com/api/new?name=json-to-typescript&url=https://gist.githubusercontent.com/mmustra/c544b9ae06baea89b95257f752f453d4/raw/f80cde38dc04857b558b5bc35023b8c92131e7f2/json-to-typescript.js\")\r\n\r\n```js\r\n// Name: JSON to TypeScript\r\n// Description: Copy JSON and paste it to TypeScript interfaces\r\n// Author: Marin Muštra\r\n// LinkedIn: https://www.linkedin.com/in/marin-mustra\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst JsonToTS = await npm('json-to-ts');\r\nconst json = await paste();\r\nlet types = '';\r\n\r\ntry {\r\n const obj = JSON.parse(json);\r\n types = `${JsonToTS(obj).join('\\n\\n')}\\n`;\r\n} catch (error) {\r\n const hint = `${error.message}`;\r\n await editor({ hint, input: json, description: 'ERROR', readOnly: true, lineNumbers: 'on' });\r\n\r\n exit();\r\n}\r\n\r\nawait setSelectedText(types);\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-28T01:14:03Z"},{"preview":"docs","menu":"Open Project","description":"","shortcut":"cmd shift .","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1067","url":"https://gist.githubusercontent.com/Ambushfall/6bf540b63a5b4fc942d65deaec04f149/raw/5f0d048c608dc35558c8b661cb6c9c455c69efcb/dev-project.js","title":"Win32 - Working Project Opener / Editor","command":"win32-working-project-opener-editor","content":"\r\n[Open dev-project in Script Kit](https://scriptkit.com/api/new?name=dev-project&url=https://gist.githubusercontent.com/Ambushfall/6bf540b63a5b4fc942d65deaec04f149/raw/5f0d048c608dc35558c8b661cb6c9c455c69efcb/dev-project.js\")\r\n\r\n```js\r\n// Preview: docs\r\n// Menu: Open Project\r\n// Description: Opens a project in vscode\r\n// Shortcut: cmd shift .\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst envPath = await env('PROJECT_DIR');\r\n\r\nconst projectDir = home(envPath);\r\n\r\nconst projects = await readdir(projectDir);\r\n\r\nconst filteredProjects = projects.filter((v) => v.includes('.') ? false : v)\r\n\r\nconst project = await arg('Enter a project name:', filteredProjects);\r\n\r\nconst projectPath = path.resolve(projectDir, project);\r\n\r\nedit(projectPath)\r\n```\r\n\r\n# Todos\r\n- [ ] Make it so editor does not create an untitled file\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-27T20:23:48Z"},{"name":"Chat with CHAT GPT","description":"","avatar":"https://avatars.githubusercontent.com/u/39631861?u=ecdb890a3c471983cfaf94b596c0e57d9149f670&v=4","user":"gammaSpeck","author":"Madhu KM","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1062","url":"","title":"Ask a single query with CHAT GPT","command":"ask-a-single-query-with-chat-gpt","content":"\r\n![Screen Recording 2023-01-27 at 11 20 43 PM](https://user-images.githubusercontent.com/39631861/215159017-6eef65a8-27d7-4203-8a45-e0dec4d8feea.gif)\r\n\r\n\r\n[Open chat-gpt-simple in Script Kit](https://scriptkit.com/api/new?name=chat-gpt-simple&url=https://gist.githubusercontent.com/gammaSpeck/1470334d43185b67f009bf32fdc25d23/raw/905260c7c4892d981d78e24de251ac41fbd8ca0c/chat-gpt-simple.ts)\r\n\r\n\r\n\r\n---\r\n\r\n## Prequisites:\r\n1. Get an API KEY from https://beta.openai.com/account/api-keys. \r\n2. Paste that API key inside your `.kenv/.env` file as `OPENAI_API_KEY=`\r\n3. Running the script will work as expected.\r\n\r\n\r\n## Script snippet\r\n\r\n```js\r\n// Name: Chat with CHAT GPT\r\n// Description: Ask CHAT GPT Anything\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { Configuration, OpenAIApi } = await npm(\"openai\");\r\n// Use below line for better TS hints\r\n// import { Configuration, OpenAIApi } from \"openai\";\r\n\r\nconst configuration = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\")\r\n});\r\n\r\nconst openAI = new OpenAIApi(configuration);\r\n\r\nconst prompt = await editor(\"\");\r\n\r\nconst completion = await openAI.createCompletion({\r\n model: \"text-davinci-003\",\r\n prompt,\r\n temperature: 0,\r\n max_tokens: 4069\r\n});\r\n\r\nconst choices = completion.data.choices;\r\n\r\nconst response = choices[0].text;\r\n// Uncomment if you want your computer to speak the response\r\n// say(response);\r\n\r\nconst containerClassName =\r\n \"flex justify-center items-center text-2xl h-full p-10\";\r\n\r\nawait div(response, containerClassName);\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-27T11:27:42Z"},{"name":"JIRA","description":"","author":"Orhan Erday","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/98099231?u=007356cffb73e56b790e3409a06e0b2938102b8d&v=4","user":"orhan-erday","discussion":"https://github.com/johnlindquist/kit/discussions/1055","url":"","title":"JIRA JQL Query Script: Easily Search and View Issues in Your JIRA Instance","command":"jira-jql-query-script-easily-search-and-view-issues-in-your-jira-instance","content":"This script allows the user to run a JIRA JQL (Jira Query Language) query and display the results. The script prompts the user to enter a JQL query, or it uses a default query that searches for all issues assigned to the current user that are not closed or marked as \"Done\" or \"Development Done\", and orders the results by the date they were created. The script then uses the JIRA API to perform the search and display the results in a list format. The user can select one of the issues from the list and the script will open the selected issue in the browser. The script requires the user to set the `JIRA_HOST`, `JIRA_USERNAME`, and `JIRA_PWD` environment variables to connect to their JIRA instance\r\n\r\n[Open with ScriptKIT](https://scriptkit.com/api/new?name=search-starred-repos&url=https://gist.github.com/orhan-erday/6582bb1b4a60f06dcb8ba4831b6f2c96) \r\n\r\n````javascript\r\n\r\n// Name: JIRA\r\n// Description: Run JQL query\r\n// Author: Orhan Erday\r\n// Twitter: @orhan_erday\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet jql = await arg(\"Enter the JQL, or press enter if you want to get Your work.\")\r\n\r\nif (jql == \"\") {\r\n jql = `assignee=currentUser() and status != Closed and status != \"Done\" and status != \"Development Done\" order by created DESC`\r\n}\r\n\r\nlet jira = {\r\n host: await env(\"JIRA_HOST\", \"example.atlassian.net\"),\r\n jql: jql \r\n}\r\n\r\nlet response = await get(`https://${jira.host}/rest/api/2/search?jql=${jira.jql}`, {\r\n auth: {\r\n username: await env(\"JIRA_USERNAME\"),\r\n password: await env(\"JIRA_PWD\")\r\n }\r\n})\r\n\r\n// inspect(response.data.issues)\r\n\r\nlet key = await arg(\r\n `Select JIRA:`,\r\n response.data.issues.map(({\r\n fields,\r\n key\r\n }) => {\r\n return {\r\n name: key,\r\n description: fields.summary,\r\n value: key,\r\n preview: async () => {\r\n\r\n\r\n return md(\r\n `\r\n# ${fields.summary}\r\n\r\n## Description \r\n\r\n${fields.description} \r\n\r\n## Issue Type\r\n\r\n${fields.parent.fields.status.name} \r\n\r\n## Status\r\n\r\n${fields.parent.fields.issuetype.name} `)\r\n },\r\n }\r\n })\r\n)\r\n\r\nawait $`start https://${jira.host}/browse/${key}`\r\n````","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-25T13:23:19Z"},{"name":"Github Notifications","description":"","author":"El Hadji Malick Seck","twitter":"takanome_dev","github":"@TAKANOME-DEV","avatar":"https://avatars.githubusercontent.com/u/79809121?u=94c6b55ab061ac71d15afbceb0542582be7dec35&v=4","user":"takanome-dev","discussion":"https://github.com/johnlindquist/kit/discussions/1051","url":"","title":"Browse, preview and open Github notifications","command":"browse-preview-and-open-github-notifications","content":"\r\n[Open check-github-notifications in Script Kit](https://scriptkit.com/api/new?name=check-github-notifications&url=https://gist.githubusercontent.com/TAKANOME-DEV/d516ef95bf67c2cfd5e09bc057dd7487/raw/f60143ad1365b295eaac3ca5138e38e1504bc45e/check-github-notifications.ts\")\r\n\r\nI created this script to quickly browse through my Github notifications, preview them and jump on them if needed without leaving my vscode or other programs. It's heavenly inspired by @bastibuck ([Search and open starred GitHub repos script](https://github.com/johnlindquist/kit/discussions/1044))\r\n\r\n![brandbird-jpg-1](https://user-images.githubusercontent.com/79809121/214446206-232c80c9-e42c-44a6-9382-2675edbd45d0.jpg)\r\n\r\n\r\n```js\r\n// Name: Github Notifications\r\n// Description: Browse, preview and open your GitHub notifications\r\n// Author: TAKANOME DEV\r\n// Twitter: @takanome_dev\r\n// Github: @TAKANOME-DEV\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { Octokit }: typeof import(\"octokit\") = await npm(\"octokit\")\r\nimport { Endpoints } from \"@octokit/types\"\r\n\r\ntype Notification = Endpoints[\"GET /notifications\"][\"response\"][\"data\"][number]\r\ntype IssueComment = Endpoints[\"GET /repos/{owner}/{repo}/issues/{issue_number}/comments\"][\"response\"][\"data\"][number]\r\n\r\n\r\nconst prIcon = `\r\n`\r\nconst chatIcon = `\r\n`\r\nconst commitIcon = `\r\n`\r\nconst openIssuePrIcon = `\r\n`\r\nconst releaseIcon = `\r\n`\r\n\r\nconst AUTH_TOKEN = await env(\"GH_CLASSIC_TOKEN\", {\r\n hint: md(`\r\n Create a [personal access token](https://github.com/settings/tokens) with the \\`notifications\\` scope.\r\n `),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst octokit = new Octokit({\r\n auth: AUTH_TOKEN,\r\n})\r\n\r\nconst formattedDate = (date: string) => {\r\n const d = new Date(date)\r\n return `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`\r\n}\r\n\r\nconst renderIcon = (type: string) => {\r\n switch (type) {\r\n case 'PullRequest':\r\n return prIcon\r\n case 'Issue':\r\n return openIssuePrIcon\r\n case 'Commit':\r\n return commitIcon\r\n case 'Discussion':\r\n return chatIcon\r\n case 'Release':\r\n return releaseIcon\r\n default:\r\n return '' \r\n }\r\n}\r\n\r\nconst authorAssociationEmoji = (authorAssociation: string) => {\r\n switch (authorAssociation) {\r\n case 'OWNER':\r\n return '👑'\r\n case 'COLLABORATOR':\r\n return '👨👩👧👦'\r\n case 'CONTRIBUTOR':\r\n return '👨💻'\r\n case 'FIRST_TIMER':\r\n return '👶'\r\n case 'FIRST_TIME_CONTRIBUTOR':\r\n return '👶'\r\n case 'MANNEQUIN':\r\n return '🤖'\r\n case 'MEMBER':\r\n return '👪'\r\n default:\r\n return ''\r\n }\r\n}\r\n\r\nconst getUrl = (notification: Notification): string => {\r\n switch (notification.subject.type) {\r\n case \"Discussion\":\r\n return `${notification.repository.html_url}/discussions`\r\n case \"Release\":\r\n return `${notification.repository.html_url}/releases/tag/${notification.subject.title}`\r\n case \"Issue\":\r\n return `${notification.repository.html_url}/issues/${notification.subject.url?.split('/').pop()}`\r\n case \"PullRequest\":\r\n return `${notification.repository.html_url}/pull/${notification.subject.url?.split('/').pop()}`\r\n }\r\n}\r\n\r\nconst notificationReason = await arg(\r\n 'Choose a notification reason:',\r\n [\r\n { name: '🛎 All', value: 'all', description: 'All notifications' },\r\n { name: '🤹 Assign', value: 'assign', description: 'You were assigned to the thread' },\r\n { name: '👨💻 Author', value: 'author', description: 'You created the thread' },\r\n { name: '💬 Comment', value: 'comment', description: 'You commented on the thread' },\r\n { name: '📨 Invitation', value: 'invitation', description: 'You were invited to a repository' },\r\n { name: '👨🏫 Manual', value: 'manual', description: 'You are watching the repository' },\r\n { name: '👋 Mention', value: 'mention', description: 'You were @mentioned in the thread' },\r\n { name: '👀 Review Requested', value: 'review_requested', description: 'You were requested to review a pull request' },\r\n { name: '🚨 Security Alert', value: 'security_alert', description: 'A security alert was published' },\r\n { name: '🔁 State Change', value: 'state_change', description: 'The thread was marked as read' },\r\n { name: '👍 Subscribed', value: 'subscribed', description: 'You are subscribed to the thread' },\r\n { name: '👨👩👧👦 Team Mention', value: 'team_mention', description: 'You were @mentioned in the thread' },\r\n ])\r\n\r\nconst notifications = await octokit.paginate(\"GET /notifications\")\r\n\r\nconst notifs = await arg(\r\n \"Search through notifications...\",\r\n notifications.filter((n) => {\r\n if (notificationReason === 'all') {\r\n return true\r\n }\r\n return n.reason === notificationReason\r\n }).map((n) => {\r\n\r\n return {\r\n // TODO: render icon in front of name\r\n name: n.repository.full_name,\r\n value: getUrl(n),\r\n description: `${n.last_read_at ? '' : '🔔'} ${n.subject.title} - _${formattedDate(n.updated_at)}_`,\r\n icon: n.repository.owner.avatar_url,\r\n preview: async () => {\r\n const response = await octokit.paginate(\"Get /repos/{owner}/{repo}/issues/{issue_number}/comments\", {\r\n owner: n.repository.owner.login,\r\n repo: n.repository.name,\r\n issue_number: n.subject.url.split('/').pop(),\r\n })\r\n\r\n const comments = response.map((comment) => {\r\n return `## @${comment.user.login} - ${formattedDate(comment.updated_at)} ${comment.updated_at === comment.created_at ? '' : '🔁'} ${comment.author_association ? `- ${comment.author_association}` : ''} ${authorAssociationEmoji(comment.author_association)} \\n\\n ${comment.body}`\r\n }).join('\\n\\n')\r\n\r\n return md(`
${renderIcon(n.subject.type)} ${n.subject.title}
\\n\\n ${comments}`)\r\n }\r\n };\r\n })\r\n);\r\n\r\nbrowse(notifs);\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-24T23:48:10Z"},{"name":"Deep URL decode","description":"","avatar":"https://avatars.githubusercontent.com/u/8796757?u=cb54a30aceeb3c98faaafd302dbcd5187d7eb40f&v=4","user":"Cauen","author":"Emanuel","twitter":"cauenor","discussion":"https://github.com/johnlindquist/kit/discussions/1046","url":"","title":"Deep URL Decode","command":"deep-url-decode","content":"Parses complex links like this\r\n```\r\nhttps://google.com/notify/messages/1295812905/click?signature=10295801298586296300235&url=https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\r\n```\r\n\r\nInto:\r\n\r\n```json\r\n{\r\n \"1: URL Original\": \"https://google.com/notify/messages/1295812905/click?signature=10295801298586296300235&url=https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\",\r\n \"2: URL Decoded\": \"https://google.com/notify/messages/1295812905/click?signature=10295801298586296300235&url=https://google.com/users/link/ZW1hbnVlbC5-9AF80a98fg8vbQ==MTY3NDQzMjg2NA==6a8f74a552?redirect_to=https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795&utm_source=escalation_mailer&utm_medium=email&utm_campaign=resolved\",\r\n \"3: URL Params\": {\r\n \"signature\": \"10295801298586296300235\",\r\n \"url\": \"https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\"\r\n },\r\n \"4: Children\": {\r\n \"url\": {\r\n \"1: URL Original\": \"https%3A%2F%2Fgoogle.com%2Fusers%2Flink%2FZW1hbnVlbC5-9AF80a98fg8vbQ%3D%3DMTY3NDQzMjg2NA%3D%3D6a8f74a552%3Fredirect_to%3Dhttps%253A%252F%252Fgoogle.com%252Fteam%252F832678%252Fincidents%252F331734795%26utm_source%3Descalation_mailer%26utm_medium%3Demail%26utm_campaign%3Dresolved\",\r\n \"2: URL Decoded\": \"https://google.com/users/link/ZW1hbnVlbC5-9AF80a98fg8vbQ==MTY3NDQzMjg2NA==6a8f74a552?redirect_to=https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795&utm_source=escalation_mailer&utm_medium=email&utm_campaign=resolved\",\r\n \"3: URL Params\": {\r\n \"redirect_to\": \"https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795\",\r\n \"utm_source\": \"escalation_mailer\",\r\n \"utm_medium\": \"email\",\r\n \"utm_campaign\": \"resolved\"\r\n },\r\n \"4: Children\": {\r\n \"redirect_to\": {\r\n \"1: URL Original\": \"https%3A%2F%2Fgoogle.com%2Fteam%2F832678%2Fincidents%2F331734795\",\r\n \"2: URL Decoded\": \"https://google.com/team/832678/incidents/331734795\",\r\n \"3: URL Params\": {},\r\n \"4: Children\": {}\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\n\r\n[Install](https://scriptkit.com/api/new?name=deep-url-decode&url=https://gist.githubusercontent.com/Cauen/80db9eee7ce5c04969e73ef46159c18d/raw/6c6f88772e9d254744ccd003512af299e486f8fc/deep-url-decode.ts)\r\n\r\n```ts\r\n// Name: Deep URL decode\r\n// Description: Sometimes you want to know what inside a URL, use this to show as a deep json...\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst decode = (str: string) => decodeURIComponent((str + '').replace(/\\+/g, '%20'))\r\n\r\nfunction getParamsFromUrl(url: string): Record | void {\r\n if (url.startsWith(\"https%\")) return getParamsFromUrl(decode(url))\r\n\r\n try {\r\n if (typeof url === 'string') {\r\n let params = url.split('?');\r\n let eachParamsArr = params[1].split('&');\r\n let obj: Record = {};\r\n if (eachParamsArr && eachParamsArr.length) {\r\n eachParamsArr.map(param => {\r\n let keyValuePair = param.split('=')\r\n let key = keyValuePair[0];\r\n let value = keyValuePair[1];\r\n obj[key] = value;\r\n })\r\n }\r\n return obj;\r\n }\r\n } catch (err) {\r\n // return getParamsFromUrl(decode(url))\r\n return {}\r\n }\r\n}\r\n\r\ninterface DecodeType {\r\n ['1: URL Original']: string\r\n ['2: URL Decoded']: string\r\n ['3: URL Params']: void | Record\r\n ['4: Children']: Record\r\n}\r\nfunction decodeDeep(urlString: string): DecodeType {\r\n const decodedUrl = decode(urlString)\r\n const paramsFromUrl = getParamsFromUrl(urlString)\r\n\r\n return {\r\n ['1: URL Original']: urlString,\r\n ['2: URL Decoded']: decodedUrl,\r\n ['3: URL Params']: paramsFromUrl,\r\n ['4: Children']: (() => {\r\n if (!paramsFromUrl) return {}\r\n const entries = Object.entries(paramsFromUrl)\r\n return entries.reduce((current, [paramKey, paramValue]) => {\r\n const itemIsUrl = paramValue.startsWith(\"http\")\r\n if (!itemIsUrl) return current\r\n const decoded = decodeDeep(paramValue)\r\n if (!decoded) return current\r\n // return current\r\n return { ...current, [paramKey]: decoded }\r\n }, {})\r\n })()\r\n }\r\n}\r\n\r\nlet url = await arg(\"enter URL\")\r\n\r\ninspect(decodeDeep(url))\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-23T16:10:18Z"},{"name":"Search starred GitHub repos","description":"","author":"Basti Buck","twitter":"bastibuck","avatar":"https://avatars.githubusercontent.com/u/6306291?u=248fb2425c610afbf4805bbfe0b9529104bbeef8&v=4","user":"bastibuck","discussion":"https://github.com/johnlindquist/kit/discussions/1044","url":"","title":"Search and open starred GitHub repos","command":"search-and-open-starred-github-repos","content":"\r\n[Open search-starred-repos in Script Kit](https://scriptkit.com/api/new?name=search-starred-repos&url=https://gist.githubusercontent.com/bastibuck/44f37ed22722aac25f8eb363d06a4160/raw/8cbdba2058829a2be31d948c795f0abbacefaae2/search-starred-repos.ts)\r\n\r\nThis script allows searching and opening your (or anyone's) starred repositories from GitHub. It works by setting up a second, scheduled script that downloads and caches the repos in `db()` so searching is quick every time afterwards. \r\n\r\nWhen running this script for the first time it checks if the cache script exists and otherwise creates it for you with the given schedule. On first run it doesn't have any cached repos yet and therefor tells you to run the cache-script manually once.\r\n\r\nOn consecutive runs the search script uses cached starred repositories to display choices and opens the selection in the browser.\r\n\r\n\r\n```ts\r\n// Name: Search starred GitHub repos\r\n// Description: Search and filter starred GitHub repositories\r\n// Author: Basti Buck\r\n// Twitter: @bastibuck\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport { Choices } from \"@johnlindquist/kit\";\r\n\r\nconst CACHE_SCRIPT_NAME = \"cache-starred-repos.ts\";\r\n\r\nconst { isValidCron }: typeof import(\"cron-validator\") = await npm(\r\n \"cron-validator\"\r\n);\r\n\r\n// setup caching script if not installed\r\nconst installedScripts = await readdir(home(kenvPath(\"/scripts\")));\r\n\r\nif (!installedScripts.includes(CACHE_SCRIPT_NAME)) {\r\n const cronSchedule = await arg({\r\n placeholder:\r\n \"Choose schedule or define your own schedule using CRON syntax\",\r\n strict: false,\r\n choices: getCronChoices,\r\n validate: validateCRON,\r\n });\r\n\r\n await appendFile(\r\n home(kenvPath(\"/scripts\"), CACHE_SCRIPT_NAME),\r\n buildCacheScript({ cronSchedule })\r\n );\r\n\r\n await div(\r\n `
\r\n
\r\n Looks like this is your first run.\r\n
\r\n\r\n
I've setup a caching script for you.
\r\n
\r\n This script will run on the defined schedule and cache your starred repos for faster searching. If you need to run the caching manually you can do so from Kit.\r\n
\r\n\r\n
\r\n To initialize your cache now, run Cache starred GitHub repos.\r\n
\r\n
`\r\n );\r\n\r\n exit(0);\r\n}\r\n\r\n// show filterable starred repos\r\nlet { cachedStars } = await db(\"starred-repos\", { cachedStars: [] });\r\n\r\nconst star = await arg(\r\n \"Search starred repos...\",\r\n cachedStars.map((star) => {\r\n return {\r\n name: star.full_name,\r\n value: star.html_url,\r\n description: `⭐ ${star.stargazers_count} ${\r\n star.description ? \" | \" + star.description : \"\"\r\n }`,\r\n icon: star.owner.avatar_url,\r\n };\r\n })\r\n);\r\n\r\nbrowse(star);\r\n\r\n// helper functions\r\n\r\nfunction getCronChoices(): Choices {\r\n return [\r\n {\r\n name: \"0 12 * * *\",\r\n value: \"0 12 * * *\",\r\n description: \"Daily at noon\",\r\n },\r\n {\r\n name: \"0 8 * * *\",\r\n value: \"0 8 * * *\",\r\n description: \"Daily at 8:00 am\",\r\n },\r\n {\r\n name: \"0 8 * * 1\",\r\n value: \"0 8 * * 1\",\r\n description: \"Weekly on Monday morning\",\r\n },\r\n ];\r\n}\r\n\r\nfunction validateCRON(cron: string) {\r\n return isValidCron(cron)\r\n ? true\r\n : \"Invalid CRON syntax. Check crontab.guru for help\";\r\n}\r\n\r\nfunction buildCacheScript({ cronSchedule }: { cronSchedule: string }) {\r\n return `// Name: Cache starred GitHub repos\r\n// Description: Cache starred GitHub repositories for future operations\r\n// Author: Basti Buck\r\n// Twitter: @bastibuck\r\n\\/\\/ Schedule: ${cronSchedule}\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { z }: typeof import(\"zod\") = await npm(\"zod\");\r\nconst { Octokit }: typeof import(\"octokit\") = await npm(\"octokit\");\r\nimport { Endpoints } from \"@octokit/types\";\r\n\r\nconst AUTH_TOKEN = await env(\"GITHUB_AUTH_TOKEN\", {\r\n hint: md(\r\n \\`Create a [personal access token](https://github.com/settings/tokens) with the \\\\\\`read:user\\\\\\` scope.\\`\r\n ),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst USERNAME = await env(\"GITHUB_USERNAME\", {\r\n hint: md(\\`Your GitHub username\\`),\r\n});\r\n\r\ntype Star = Endpoints[\"GET /user/starred\"][\"response\"][\"data\"][number];\r\n\r\nconst cachedStarSchema = z.array(\r\n z.object({\r\n full_name: z.string(),\r\n html_url: z.string(),\r\n stargazers_count: z.number(),\r\n description: z.string().optional().nullish(),\r\n owner: z.object({\r\n avatar_url: z.string(),\r\n }),\r\n })\r\n);\r\n\r\nconst octokit = new Octokit({\r\n auth: AUTH_TOKEN,\r\n});\r\n\r\nlet { cachedStars, write } = await db(\"starred-repos\", {\r\n cachedStars: [],\r\n});\r\n\r\nconst stars = await octokit.paginate(\\`GET /users/\\${USERNAME}/starred\\`);\r\n\r\n_.remove(cachedStars, () => true);\r\n\r\ncachedStars.push(...cachedStarSchema.parse(stars));\r\n\r\nawait write();\r\n`;\r\n}\r\n\r\n\r\n```\r\n\r\nAny feedback appreciated 😀 \r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-23T13:40:00Z"},{"name":"sPoNgEcAsE Text","description":"","author":"Luke Secomb","avatar":"https://avatars.githubusercontent.com/u/13529535?u=38ecbbdc3611ee1cb873b67ab948558e141d3748&v=4","user":"lukethacoder","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1042","url":"","title":"sPoNgEcAsE / aLt CaPS","command":"spongecase-alt-caps","content":"A small silly script to convert inputted text to sPoNgEcAsE (or aLt CaPS). Saves to your clipboard ready to paste wherever you like.\r\n\r\n```typescript\r\n// Name: sPoNgEcAsE Text\r\n// Description: Converts input text to sPoNgEcAsE (or aLt CaPS)\r\n// Author: Luke Secomb\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction sPoNgEbObCaSe(inputText: string) {\r\n return inputText\r\n .split('')\r\n .map((char, index) => (index % 2 ? char.toUpperCase() : char.toLowerCase()))\r\n .join('');\r\n}\r\n\r\nconst userInput = await arg('Enter text');\r\ncopy(sPoNgEbObCaSe(userInput));\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-23T09:22:58Z"},{"menu":"Open Chrome Bookmark","description":"","author":"kyoyoung keum","github":"@youngkyo0504","avatar":"https://avatars.githubusercontent.com/u/78121870?u=247cb0fe42c53610056202437f09154f357fa306&v=4","user":"youngkyo0504","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1041","url":"https://gist.githubusercontent.com/youngkyo0504/eb5d1e73abe685672f8868d949b98831/raw/38b73f53d1194458418bf7e7d9feb5fa0b64b9fb/open-crhome-bookmark.js","title":"open chrome bookmark (show all nested bookmark)","command":"open-chrome-bookmark-show-all-nested-bookmark","content":"This displays all of your bookmarks. (including nested bookmark)\r\n\r\n[Open open chrome bookmark in Script Kit](https://scriptkit.com/api/new?name=open-chrome-bookmark&url=https://gist.githubusercontent.com/youngkyo0504/eb5d1e73abe685672f8868d949b98831/raw/38b73f53d1194458418bf7e7d9feb5fa0b64b9fb/open-crhome-bookmark.js)\r\n\r\n\r\n```javascript\r\nimport \"@johnlindquist/kit\";\r\n\r\n// Menu: Open Chrome Bookmark\r\n// Description: List Chrome Bookmarks. Then open tab.\r\n// Author: kyo young\r\n// GitHub: @youngkyo0504\r\n\r\nlet rawBookmarks = await readFile(\r\n home(\"Library/Application Support/Google/Chrome/Default/Bookmarks\"),\r\n \"utf8\"\r\n);\r\n\r\nconst parsedBookmarks = JSON.parse(rawBookmarks);\r\nconst bookmarkStructure = parsedBookmarks.roots.bookmark_bar.children;\r\n\r\nconst bookmarks = (function flatten(\r\n bookmarkElements\r\n) {\r\n return bookmarkElements.reduce((acc, cur) => {\r\n if (cur.type === \"folder\") {\r\n return [...acc, ...flatten(cur.children)];\r\n }\r\n\r\n return [...acc, cur];\r\n }, []);\r\n})(bookmarkStructure);\r\n\r\nlet bookmarkChoices = bookmarks.map(({ name, url }) => {\r\n return {\r\n name: name,\r\n description: url,\r\n value: url,\r\n };\r\n});\r\n\r\nlet bookmarksAndOpen = [...bookmarkChoices];\r\nlet choices = _.uniqBy(bookmarksAndOpen, \"name\");\r\n\r\nlet url = await arg(\"Oepn Chrome tab:\", choices);\r\n\r\nfocusTab(url);\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-23T01:56:17Z"},{"name":"Port kill","description":"","author":"kyoyoung keum","github":"@youngkyo0504","avatar":"https://avatars.githubusercontent.com/u/78121870?u=247cb0fe42c53610056202437f09154f357fa306&v=4","user":"youngkyo0504","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1038","url":"","title":"port kill","command":"port-kill","content":"[Open Port kill in Script Kit](https://scriptkit.com/api/new?name=time-from-now&url=https://gist.githubusercontent.com/youngkyo0504/f8632d4775b7964188dc714538c81991/raw/b38e4402c31c320d427077feff73aba65f60a190/port-kill.js\r\n)\r\n\r\n|Success|Fail|\r\n|---|---|\r\n|||\r\n\r\n\r\n```js\r\n// Name: Port kill\r\n// Description: Enter port number to kill process listening on port.\r\n// Author: kyo young\r\n// GitHub: @youngkyo0504\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst killPort = await npm('kill-port');\r\nconst port = await arg('Enter port to kill')\r\nconst containerClassName = 'flex justify-center items-center text-4xl h-full'\r\n\r\ntry {\r\n await killPort(port, 'tcp')\r\n await div(`🤖 listening on port ${port} has been killed.`,containerClassName);\r\n} catch (error) {\r\n console.error(error);\r\n await div(`\r\n 🛰️ ${error.message}\r\n `,containerClassName)\r\n}\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-22T11:50:57Z"},{"name":"Open GitHub Repository","description":"","author":"Michael Lyon","twitter":"__mlyon","avatar":"https://avatars.githubusercontent.com/u/37197876?u=de8de604e5a8d9a0b70487f3e9d5c89e506d3bbf&v=4","user":"m1yon","discussion":"https://github.com/johnlindquist/kit/discussions/1024","url":"","title":"Open GitHub Repository","command":"open-github-repository","content":"Easily view and open GitHub repositories.\r\n\r\nA simplified version of @vogelino's [\"Browse GitHub repos and perform actions on them\" script](https://github.com/johnlindquist/kit/discussions/893).\r\n\r\n___\r\n\r\n[Open open-github-repository in Script Kit](https://scriptkit.com/api/new?name=open-github-repository&url=https://gist.githubusercontent.com/m1yon/154ce4a6e16fe1540d2b65098687b97b/raw/439ef530fd2275f399019e7e42a0302b293518cb/open-github-repository.ts\")\r\n\r\n```js\r\n// Name: Open GitHub Repository\r\n// Description: Open a specific GitHub repository\r\n// Author: Michael Lyon\r\n// Twitter: @__mlyon\r\n\r\nimport '@johnlindquist/kit'\r\n\r\nconst { Octokit } = await npm('octokit')\r\n\r\ninterface RawRepositoryType {\r\n id: string\r\n name: string\r\n html_url: string\r\n full_name: string\r\n visibility: string[]\r\n description: string\r\n homepage?: string\r\n owner: {\r\n login: string\r\n }\r\n open_issues_count: number\r\n}\r\n\r\ninterface OptionType {\r\n name: string\r\n descritpion?: string\r\n preview?: string\r\n value: ValueType\r\n}\r\n\r\nconst auth = await env(`GITHUB_ACCESS_TOKEN`, 'Enter your GitHub access token')\r\nconst octokit = new Octokit({ auth })\r\n\r\nconst {\r\n data: { login },\r\n} = await octokit.rest.users.getAuthenticated()\r\n\r\nconst mapRawRepo = (repo: RawRepositoryType) => ({\r\n name: repo.full_name,\r\n description: [\r\n repo.visibility[0].toUpperCase() + repo.visibility.slice(1),\r\n repo.description,\r\n repo.homepage,\r\n ]\r\n .filter(Boolean)\r\n .join(' · '),\r\n value: repo,\r\n})\r\n\r\nconst mapReposResponse = (response: { data: RawRepositoryType[] }) =>\r\n (response.data || []).map(mapRawRepo)\r\n\r\nasync function fetchAllRepos() {\r\n return await octokit.paginate(\r\n octokit.rest.repos.listForAuthenticatedUser,\r\n { sort: 'updated', per_page: 100 },\r\n mapReposResponse,\r\n )\r\n}\r\n\r\nasync function fetchRecentRepos() {\r\n const res = await octokit.request('GET /user/repos', {\r\n sort: 'updated',\r\n per_page: 50,\r\n })\r\n return res.data\r\n}\r\n\r\nasync function fetchOwnerRepos() {\r\n const res = await octokit.request('GET /user/repos', {\r\n sort: 'updated',\r\n per_page: 50,\r\n affiliation: 'owner',\r\n })\r\n return res.data\r\n}\r\n\r\nfunction getTabHandler(getter: () => Promise[]>) {\r\n return async function handler() {\r\n const repos = await getter()\r\n const repoSelected = await arg(`Hello ${login}. Search for a repo`, repos)\r\n\r\n if (repos.length === 0) {\r\n await div(`
No repos
`)\r\n await handler()\r\n }\r\n\r\n await browse(repoSelected.html_url)\r\n exit()\r\n }\r\n}\r\n\r\nconst recentTab = getTabHandler(fetchRecentRepos)\r\nonTab('Recent', recentTab)\r\nonTab('Owner', getTabHandler(fetchOwnerRepos))\r\nonTab('All', getTabHandler(fetchAllRepos))\r\nawait recentTab()\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-01-20T21:35:58Z"}]
+[{"avatar":"https://avatars.githubusercontent.com/u/52321532?u=ddb1a0825917a338122fd15542276e3b29f2f4af&v=4","user":"awakenedhaggis","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1390","url":"","title":"Query Kagi FastGPT","command":"query-kagi-fastgpt","content":"Wanted to make something similar to the OpenAi integrations already made for people who are subscribed to Kagi\r\n\r\n[Gist here](https://gist.github.com/awakenedhaggis/bd9dbf2421325117f7e5c20f62e1c99f)\r\n\r\n`Enter` to submit a query\r\n`Ctrl/Command + R` to rerun a query\r\n`Ctrl/Command + W` to close the window","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-12-04T23:37:23Z"},{"name":"Shortcut to Speakers","shortcut":"cmd shift m","avatar":"https://avatars.githubusercontent.com/u/3072458?u=cb1f8ec2b30c2c745c8f33c8225dee605b5ceab1&v=4","user":"AquiGorka","author":"Gorka Ludlow","twitter":"AquiGorka","discussion":"https://github.com/johnlindquist/kit/discussions/1388","url":"","title":"Keyboard shortcut to switch audio output to speakers","command":"keyboard-shortcut-to-switch-audio-output-to-speakers","content":"Existing alias: `alias smac=\"SwitchAudioSource -s \\\"MacBook Pro Speakers\\\"\"` (uses [switchaudio-osx](https://github.com/deweller/switchaudio-osx))\r\n\r\nScript:\r\n\r\n```\r\n// Name: Shortcut to Speakers\r\n// Shortcut: cmd shift m\r\nimport \"@johnlindquist/kit\"\r\nawait $`/bin/zsh -lic smac`\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-12-04T17:37:52Z"},{"name":"Shortcut to Headphones","shortcut":"cmd shift h","avatar":"https://avatars.githubusercontent.com/u/3072458?u=cb1f8ec2b30c2c745c8f33c8225dee605b5ceab1&v=4","user":"AquiGorka","author":"Gorka Ludlow","twitter":"AquiGorka","discussion":"https://github.com/johnlindquist/kit/discussions/1387","url":"","title":"Keyboard shortcut to switch audio output to headphones","command":"keyboard-shortcut-to-switch-audio-output-to-headphones","content":"Existing alias: `alias shead=\"SwitchAudioSource -s \\\"External Headphones\\\"\"` (uses [switchaudio-osx](https://github.com/deweller/switchaudio-osx))\r\n\r\nScript:\r\n```\r\n// Name: Shortcut to Headphones\r\n// Shortcut: cmd shift h\r\nimport \"@johnlindquist/kit\"\r\nawait $`/bin/zsh -lic shead`\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-12-04T17:37:11Z"},{"name":"Dummy Data","description":"","author":"Nghia Vu","avatar":"https://avatars.githubusercontent.com/u/80865148?u=3b4757858e6abb48bc4fff9a133be4f009deaafe&v=4","user":"NGH14","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1373","url":"","title":"Generate Dummy Data","command":"generate-dummy-data","content":"[Install dummy-data](https://scriptkit.com/api/new?name=dummy-data&url=https://gist.githubusercontent.com/NGH14/b2efa176296362f26732bdc4fcf69402/raw/0d3ebc2ba760c720419cbd7399ed8dde62f0ca81/dummy-data.js)\r\n\r\n\r\n```js\r\n// Name: Dummy Data\r\n// Description: Generate fake data for real use-case (that #version just generate fields about person and color)\r\n// Author: Nghia Vu (Ngh14)\r\n\r\nimport '@johnlindquist/kit';\r\nimport falso from '@ngneat/falso';\r\n\r\nlet counts = 1;\r\n\r\nconst randZodiacSign = () => {\r\n\tconst zodiacSigns = [\r\n\t\t'Aries',\r\n\t\t'Taurus',\r\n\t\t'Gemini',\r\n\t\t'Cancer',\r\n\t\t'Leo',\r\n\t\t'Virgo',\r\n\t\t'Libra',\r\n\t\t'Scorpio',\r\n\t\t'Sagittarius',\r\n\t\t'Capricorn',\r\n\t\t'Aquarius',\r\n\t\t'Pisces',\r\n\t];\r\n\treturn falso.rand(zodiacSigns);\r\n};\r\n\r\nconst person = [\r\n\t{\r\n\t\tname: 'Full Name',\r\n\t\tvalue: () => falso.randFullName(),\r\n\t\tdescription: 'The complete name of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Title',\r\n\t\tvalue: () => falso.randPersonTitle(),\r\n\t\tdescription: 'The professional title of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Email',\r\n\t\tvalue: () => falso.randEmail(),\r\n\t\tdescription: 'The email address of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Phone',\r\n\t\tvalue: () => falso.randPhoneNumber(),\r\n\t\tdescription: 'The phone number of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Address',\r\n\t\tvalue: () => falso.randAddress(),\r\n\t\tdescription: 'The physical address of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Username',\r\n\t\tvalue: () => falso.randUserName(),\r\n\t\tdescription: 'The username chosen for the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Last Name',\r\n\t\tvalue: () => falso.randLastName(),\r\n\t\tdescription: 'The last name of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'First_Name',\r\n\t\tvalue: () => falso.randFirstName(),\r\n\t\tdescription: 'The first name of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Gender',\r\n\t\tvalue: () => falso.randGender(),\r\n\t\tdescription: 'The gender of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Avatar',\r\n\t\tvalue: () => falso.randAvatar(),\r\n\t\tdescription: 'The profile picture (avatar) of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Pronoun',\r\n\t\tvalue: () => falso.randPronoun(),\r\n\t\tdescription: 'The preferred pronoun of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Skill',\r\n\t\tvalue: () => falso.randSkill(),\r\n\t\tdescription: 'A skill associated with the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Password',\r\n\t\tvalue: () => falso.randPassword(),\r\n\t\tdescription: 'The password for the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Country',\r\n\t\tvalue: () => falso.randCountry(),\r\n\t\tdescription: 'The country of residence of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'City',\r\n\t\tvalue: () => falso.randCity(),\r\n\t\tdescription: 'The city of residence of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Role',\r\n\t\tvalue: () => falso.randRole(),\r\n\t\tdescription: 'The role or position held by the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Company',\r\n\t\tvalue: () => falso.randCompanyName(),\r\n\t\tdescription:\r\n\t\t\t'The name of the company associated with the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Subscription Plan',\r\n\t\tvalue: () => falso.randSubscriptionPlan(),\r\n\t\tdescription: 'The subscription plan chosen by the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Date of Birth',\r\n\t\tvalue: () => falso.randPastDate(),\r\n\t\tdescription: 'The date of birth of the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Credit Card',\r\n\t\tvalue: () => falso.randCreditCard(),\r\n\t\tdescription: 'The randomly generated credit card information of the user.',\r\n\t},\r\n\t{\r\n\t\tname: 'User Agent',\r\n\t\tvalue: () => falso.randUserAgent(),\r\n\t\tdescription:\r\n\t\t\t'The user agent of the device used by the randomly generated user.',\r\n\t},\r\n\t{\r\n\t\tname: 'IP',\r\n\t\tvalue: () => falso.randIP(),\r\n\t\tdescription: 'The randomly generated IP address of the user.',\r\n\t},\r\n\t{\r\n\t\tname: 'IP6',\r\n\t\tvalue: () => falso.randIP6(),\r\n\t\tdescription: 'The randomly generated IPv6 address of the user.',\r\n\t},\r\n\t{\r\n\t\tname: 'Zodiac Sign',\r\n\t\tvalue: () => randZodiacSign(),\r\n\t\tdescription: 'The randomly generated zodiac sign of the user.',\r\n\t},\r\n];\r\n\r\nconst color = [\r\n\t{\r\n\t\tname: 'Name',\r\n\t\tvalue: () => falso.randColor(),\r\n\t\tdescription: 'Generates a random color name.',\r\n\t},\r\n\t{\r\n\t\tname: 'HEX',\r\n\t\tvalue: () => falso.randHex(),\r\n\t\tdescription: 'Generates a random hexadecimal color code.',\r\n\t},\r\n\t{\r\n\t\tname: 'HSL',\r\n\t\tvalue: () => falso.randHsl(),\r\n\t\tdescription:\r\n\t\t\t'Generates a random HSL (Hue, Saturation, Lightness) color code.',\r\n\t},\r\n\t{\r\n\t\tname: 'RGB',\r\n\t\tvalue: () => falso.randRgb(),\r\n\t\tdescription: 'Generates a random RGB (Red, Green, Blue) color code.',\r\n\t},\r\n\t{\r\n\t\tname: 'RGBa',\r\n\t\tvalue: () => falso.randRgb({ alpha: true }),\r\n\t\tdescription:\r\n\t\t\t'Generates a random RGBA (Red, Green, Blue, Alpha) color code.',\r\n\t},\r\n];\r\n\r\nfunction filterDataField(arr, field) {\r\n\tconst filtered = {};\r\n\tarr.map(({ name, value }) => {\r\n\t\tif (field.includes(name)) {\r\n\t\t\tfiltered[name] = value();\r\n\t\t}\r\n\t});\r\n\treturn filtered;\r\n}\r\n\r\nfunction generateData({ types, fields, ...rest }) {\r\n\tconst arr = [];\r\n\r\n\tfor (let index = 0; index < counts; index++) {\r\n\t\tarr.push(filterDataField(types, fields));\r\n\t}\r\n\tsetSelectedText(JSON.stringify(arr));\r\n}\r\n\r\nlet types = await arg('Generate Random Data...', ['people', 'color']);\r\n\r\nif (types == 'people') {\r\n\tlet peopleField = await select(\r\n\t\t'Select a fields....',\r\n\t\tperson\r\n\t\t\t.sort((a, b) => a.name.localeCompare(b.name))\r\n\t\t\t.map(({ name, value, description }) => ({\r\n\t\t\t\tname,\r\n\t\t\t\tdescription,\r\n\t\t\t\tvalue: name,\r\n\t\t\t\theight: PROMPT.HEIGHT.XS,\r\n\t\t\t\tpreview: () => JSON.stringify(value()),\r\n\t\t\t})),\r\n\t);\r\n\r\n\tcounts = await arg({\r\n\t\tdescription: 'How many person records you want?',\r\n\t\tplaceholder: '1',\r\n\t});\r\n\r\n\tgenerateData({ types: person, fields: peopleField });\r\n} else if (types == 'color') {\r\n\tlet result = await arg(\r\n\t\t'Generate random color...',\r\n\t\tcolor.map(({ name, value, description }) => ({\r\n\t\t\tname,\r\n\t\t\tdescription,\r\n\t\t\tvalue: value(),\r\n\t\t\theight: PROMPT.HEIGHT.XS,\r\n\t\t\tpreview: () => value(),\r\n\t\t})),\r\n\t);\r\n\r\n\tsetSelectedText(result);\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-11-06T17:06:44Z"},{"name":"Switch Audio","description":"","author":"Nate Drake","avatar":"https://avatars.githubusercontent.com/u/73789?v=4","user":"ndrake","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1367","url":"","title":"Audio Output Switcher","command":"audio-output-switcher","content":"A Mac-only script using the [switchaudio-osx](https://github.com/deweller/switchaudio-osx) tool to quickly change audio output devices\r\n\r\n[Open switch-audio in Script Kit](https://scriptkit.com/api/new?name=switch-audio&url=https://gist.githubusercontent.com/ndrake/3c0840f03662a21900a81ec22ab734a1/raw/81b7c4c7538ca93eca953a6c34e58651f528fddf/switch-audio.js\")\r\n\r\n```js\r\n/*\r\n## Switch audio output device\r\n*/\r\n\r\n// Name: Switch Audio\r\n// Description: Switch audio output device (Mac only)\r\n// Author: Nate Drake\r\n\r\n// Install SwitchAudioSource with `brew install switchaudio-osx`\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst SwitchAudioSourcePath = '/opt/homebrew/bin/SwitchAudioSource'\r\n\r\nconst sasExists = await pathExists(SwitchAudioSourcePath)\r\n\r\nif (sasExists) {\r\n\r\n const currentOutput = await $`${SwitchAudioSourcePath} -c`\r\n const items = await $`${SwitchAudioSourcePath} -a -t output`\r\n\r\n const choices = items.stdout.trim().split(/\\r?\\n/).filter(o => o !== currentOutput.stdout.trim())\r\n\r\n let output = await arg(\r\n {\r\n placeholder: 'Pick ouput device'\r\n },\r\n choices\r\n )\r\n\r\n await $`${SwitchAudioSourcePath} -s ${output} -t output`\r\n} else {\r\n await div(md(`ERROR: Please install SwitchAudioSource`))\r\n}\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-11-01T01:28:45Z"},{"name":"Clipboard history","author":"Aldo Preciado","gitHub":"@aldirrix","shortcut":"command shift v","avatar":"https://avatars.githubusercontent.com/u/12806880?u=dd4d009aa8af0e17149a51a18969d29d1e4fd8de&v=4","user":"aldirrix","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1360","url":"","title":"Clipboard history with image preview","command":"clipboard-history-with-image-preview","content":"Hello there, first of all I would like to praise everyone that made this possible so far! I really like using kit and tweaking few scripts here and there for productivity.\r\n\r\nI was using an adapted version of the [community-available clipboard history](https://github.com/johnlindquist/kit/discussions/1120) but I was struggling with some errors regarding the database from time to time and this was also causing processes not being finished properly and running forever and forcing me to restart kit. The db file was also randomly deleted at times and other not so nice things that were basically making me having to come back from time to time.\r\n\r\n```\r\n[2023-05-22 08:51:17.374] [warn] ☠️ ERROR PROMPT SHOULD SHOW ☠️\r\n[2023-05-22 08:51:17.393] [warn] Error: ENOENT: no such file or directory, rename '/Users/aldo/.kenv/db/.clipboard-history.json.tmp' -> '/Users/aldo/.kenv/db/clipboard-history.json'\r\n```\r\n\r\nAfter dealing with it for few months, I realized we now have the `getClipboardHistory` function when the watcher is enabled so I'd like to share this with everyone so that we have a proper history paste with image preview that doesn't require the managing of a json file.\r\n\r\n```ts\r\n// Name: Clipboard history\r\n// Author: Aldo Preciado\r\n// GitHub: @aldirrix\r\n// Shortcut: command shift v\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst history = await getClipboardHistory();\r\n\r\nlet { value, type } = await arg(\"What to paste?\", () => {\r\n return history.map(({ value, type, timestamp, maybeSecret }) => {\r\n const multilinePreview = value.includes(\"\\n\")\r\n ? `
${value\r\n .split(\"\\n\")\r\n .map((line) => `
${line}
`)\r\n .join(\"\")}
`\r\n : null;\r\n\r\n const preview = type === \"image\" ? `` : multilinePreview;\r\n\r\n return {\r\n type,\r\n name: maybeSecret ? value.slice(0, 2).padEnd(10, \"*\") : value,\r\n value: {\r\n value,\r\n type,\r\n },\r\n description: timestamp,\r\n preview,\r\n };\r\n });\r\n});\r\n\r\nif (type === \"text\") {\r\n await setSelectedText(value);\r\n}\r\n\r\nif (type === \"image\") {\r\n await copyPathAsImage(value);\r\n await keystroke(\"command v\");\r\n}\r\n```\r\n\r\nOn the same note, is there a way for us to configure the `maybeSecret` property in the kit app? It seems like there has not been any discussion around it and I was wondering","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-10-16T08:25:15Z"},{"name":"Watch Screenshots Dir","watch":"~/Desktop/screenshots","description":"","avatar":"https://avatars.githubusercontent.com/u/25487857?u=8de0c1a02b8786762899a89fa547ddd29dc17c20&v=4","user":"vojtaholik","author":"Vojta Holik","twitter":"vojta_holik","discussion":"https://github.com/johnlindquist/kit/discussions/1357","url":"","title":"Screenshot tool alternative with Cloudinary","command":"screenshot-tool-alternative-with-cloudinary","content":"This is my alternative to _[insert your favorite screenshot upload tool]_. I use it in combination with [Record Screen script](https://github.com/johnlindquist/kit/discussions/1356).\r\n\r\nIt watches a `screenshots` directory on desktop and uploads any new file to Cloudinary. I have it then set to copy link to clipboard and move the file to trash. On top of that I have a dynamic route on my personal website to display the image/video along with a simple kvstore to track views (anonymously, just so that I know when someone view the file). It looks like [this](https://vojta.io/shots/1696935964744). Code for it is [here](https://github.com/vojtaholik/vojta-io-next/blob/main/src/pages/shots/%5Bpublic_id%5D.tsx).\r\n\r\nOne of nice things about Cloudinary is that if I record a video, all I have to do is replace`.mov` with `.gif` in url to get a gif.\r\n\r\nDon't forget to run following command in your terminal to change default screenshot (`cmd+shift+4`) location:\r\n```bash\r\ndefaults write com.apple.screencapture location ~/Desktop/screenshots\r\n``` \r\n\r\n[Open watch-screenshots in Script Kit](https://scriptkit.com/api/new?name=watch-screenshots&url=https://gist.githubusercontent.com/vojtaholik/3a7e5639544f2c62cbff989141f1da70/raw/c0782ea081d4afbe7bbd6e964d64f7f7b2b9fd54/watch-screenshots.js\")\r\n\r\n```js\r\n// Name: Watch Screenshots Dir\r\n// Watch: ~/Desktop/screenshots\r\n// Description: Don't forget to run following command in your terminal to set default screenshot directory in macOSX: defaults write com.apple.screencapture location ~/Desktop/screenshots\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport cloudinary from \"cloudinary\";\r\nimport trash from \"trash\";\r\n\r\nconst DIR = \"screenshots\";\r\nconst NOTIFY_SOUND_FILE_PATH = false; // home(\"Desktop/come-here-notification.mp3\");\r\nconst CUSTOM_DOMAIN = false; // 'https://vojta.io/shots/'\r\n\r\n// These are optional and automatically set by the watcher\r\nlet filePath = await arg();\r\nlet event = await arg();\r\n\r\n// Cloudinary options\r\nconst options = {\r\n public_id: `${DIR}/${Date.now()}`,\r\n unique_filename: true,\r\n use_filename: true,\r\n overwrite: true,\r\n filename_override: true,\r\n};\r\n\r\ncloudinary.v2.config({\r\n cloud_name: await env(\"CLOUDINARY_CLOUD_NAME\"),\r\n api_key: await env(\"CLOUDINARY_API_KEY\"),\r\n api_secret: await env(\"CLOUDINARY_API_SECRET\"),\r\n});\r\n\r\n// if file is added to DIR directory\r\nif (event === \"add\") {\r\n await appendFile(home(`Desktop/${DIR}/download.log`), filePath + \"\\n\");\r\n const isVideoFile = filePath.endsWith(\".mov\");\r\n\r\n await cloudinary.v2.uploader.upload(\r\n filePath,\r\n { ...options, resource_type: isVideoFile ? \"video\" : \"image\" },\r\n async (error, result) => {\r\n if (error) {\r\n console.error(\"Error uploading file:\", error);\r\n } else {\r\n if (result) {\r\n await copy(\r\n CUSTOM_DOMAIN\r\n ? `${CUSTOM_DOMAIN}${result.public_id.replace(`${DIR}/`, \"\")}`\r\n : isVideoFile\r\n ? result.url.replace(\".mov\", \".mp4\")\r\n : result.url\r\n );\r\n notify(\"✓ Uploaded to Cloudinary\");\r\n NOTIFY_SOUND_FILE_PATH &&\r\n (await playAudioFile(NOTIFY_SOUND_FILE_PATH));\r\n await trash([filePath]);\r\n }\r\n }\r\n }\r\n );\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-10-12T09:42:34Z"},{"menu":"Record Screen","shortcut":"shift cmd 5","avatar":"https://avatars.githubusercontent.com/u/25487857?u=8de0c1a02b8786762899a89fa547ddd29dc17c20&v=4","user":"vojtaholik","author":"Vojta Holik","twitter":"vojta_holik","discussion":"https://github.com/johnlindquist/kit/discussions/1356","url":"","title":"Record Screen","command":"record-screen","content":"`shift + cmd + 5` will start a screen recording session on macOS Sonoma. It's got video trimming feature and is overall pretty good. I use it in combination with my [screenshot upload script](https://github.com/johnlindquist/kit/discussions/1357).\r\n\r\n[Open record-screen in Script Kit](https://scriptkit.com/api/new?name=record-screen&url=https://gist.githubusercontent.com/vojtaholik/ded540fc8b553751887adbc03abcca90/raw/973985e7ef57eff98d825229edfe5a00145e2978/record-screen.js\")\r\n\r\n```js\r\n// Menu: Record Screen\r\n// Shortcut: shift cmd 5\r\n\r\n/** @type {import(\"@johnlindquist/kit\")} */\r\n\r\nawait applescript(`\r\n-- # Setup to do a screen recording.\r\n\r\n# tell application \"QuickTime Player\" to new screen recording\r\n\r\n-- # Start the screen recording.\r\n\r\ntell application \"System Events\" to tell process \"Screen Shot\"\r\n repeat until exists button \"Record\" of its front window\r\n delay 0.1\r\n end repeat\r\n click button \"Record\" of its front window\r\nend tell\r\n\r\n-- # Set the time in seconds you want the recording to be.\r\n\r\ndelay 2\r\n\r\n-- # Stop the recording.\r\n\r\ntell application \"System Events\" to ¬\r\n click menu bar item 1 ¬\r\n of menu bar 1 ¬\r\n of application process \"screencaptureui\"\r\n`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-10-12T09:30:50Z"},{"name":"arc-default-theme","description":"","note":"This script modifies Arc Browser's `StorableSidebar.json` file. Use at your peril.","avatar":"https://avatars.githubusercontent.com/u/150462?u=6478fa6d3285adcd99bf6819f9f7758f4de0d277&v=4","user":"kkoscielniak","author":"Krystian Kościelniak","twitter":"pankoscielniak","discussion":"https://github.com/johnlindquist/kit/discussions/1354","url":"https://gist.githubusercontent.com/kkoscielniak/524092e7812d37c1d30f0dc5aea5d0f8/raw/4b54a4dc63537e6e021766d7a03fdd98ebce03b6/arc-default-theme.ts","title":"Arc: Use default theme","command":"arc-use-default-theme","content":"This script allows for choosing the Space in [Arc Browser](https://arc.net) and reuse the same theme for every other Space.\r\n\r\n> I've created it because I dislike the default theme in Arc but still want to have the same one for every Space I have. \r\n\r\n> Note: This script modifies Arc Browser's `StorableSidebar.json` file. Use at your peril.\r\n\r\n[Open arc-default-theme in Script Kit](https://scriptkit.com/api/new?name=arc-default-theme&url=https://gist.githubusercontent.com/kkoscielniak/524092e7812d37c1d30f0dc5aea5d0f8/raw/4b54a4dc63537e6e021766d7a03fdd98ebce03b6/arc-default-theme.ts\")\r\n\r\n```js\r\n// Name: arc-default-theme\r\n// Description: Pick an Arc Browser's Space and set its theme for all the other Arc Spaces. Tested with Arc v1.10.1.\r\n// Note: This script modifies Arc Browser's `StorableSidebar.json` file. Use at your peril.\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport { readdir, readFile, writeFile } from \"node:fs/promises\";\r\nimport { homedir } from \"node:os\";\r\nimport { join } from \"node:path\";\r\n\r\nconst { rimraf } = await npm(\"rimraf\");\r\nconst psList = await npm(\"ps-list\");\r\n\r\nconst ARC_LIBRARY_PATH = join(\r\n homedir(),\r\n \"Library\",\r\n \"Application Support\",\r\n \"Arc\"\r\n);\r\n\r\nasync function listSidebarCacheFiles(): Promise {\r\n const arcFileNames = await readdir(ARC_LIBRARY_PATH);\r\n\r\n return arcFileNames.filter(\r\n (file) =>\r\n file.startsWith(\"StorableSidebar\") && file !== \"StorableSidebar.json\"\r\n ) as string[];\r\n}\r\n\r\nasync function removeSidebarCacheFiles(): Promise {\r\n const sidebarCacheFileNames = await listSidebarCacheFiles();\r\n\r\n for (const fileName of sidebarCacheFileNames) {\r\n try {\r\n await rimraf(join(ARC_LIBRARY_PATH, fileName));\r\n } catch (err) {\r\n console.error(err);\r\n }\r\n }\r\n}\r\n\r\nasync function findArcProcess(): Promise<{\r\n name: string;\r\n pid: number;\r\n}> {\r\n const processes = await psList();\r\n\r\n const arcProcess = processes.find((process) => process.name === \"Arc\");\r\n\r\n return arcProcess;\r\n}\r\n\r\nasync function killArcProcess(): Promise {\r\n const arcProcess = await findArcProcess();\r\n\r\n if (arcProcess) {\r\n process.kill(arcProcess.pid);\r\n }\r\n}\r\n\r\nasync function readStorableSidebarJson(): Promise {\r\n const storableSidebarJson = await readFile(\r\n join(ARC_LIBRARY_PATH, \"StorableSidebar.json\"),\r\n \"utf-8\"\r\n );\r\n\r\n return JSON.parse(storableSidebarJson) as StorableSidebarJson;\r\n}\r\n\r\nasync function getSourceSpaceTheme(\r\n json: StorableSidebarJson,\r\n sourceSpaceName: string\r\n): Promise {\r\n const sourceSpace: SpaceModel = json.sidebarSyncState.spaceModels.find(\r\n (spaceModel) =>\r\n typeof spaceModel !== \"string\" &&\r\n spaceModel.value?.title === sourceSpaceName\r\n ) as SpaceModel;\r\n\r\n return sourceSpace.value?.customInfo.windowTheme;\r\n}\r\n\r\nasync function getTargetSpaces(\r\n json: StorableSidebarJson,\r\n originalSpaceName: string\r\n): Promise {\r\n const itemsContainer = json.sidebar.containers.find((container) =>\r\n Object.hasOwnProperty.call(container, \"items\")\r\n );\r\n\r\n if (itemsContainer) {\r\n const spaces = itemsContainer.spaces as (string | SpaceData)[];\r\n\r\n return spaces.filter(\r\n (space) => typeof space !== \"string\" && space.title !== originalSpaceName\r\n ) as SpaceData[];\r\n }\r\n}\r\n\r\nasync function getTargetSpacesSynced(\r\n json: StorableSidebarJson,\r\n originalSpaceName: string\r\n): Promise {\r\n return json.sidebarSyncState.spaceModels.filter(\r\n (spaceModel) =>\r\n typeof spaceModel !== \"string\" &&\r\n spaceModel.value?.title !== originalSpaceName\r\n ) as SpaceModel[];\r\n}\r\n\r\nasync function writeStorableSidebarJson(\r\n json: StorableSidebarJson\r\n): Promise {\r\n await removeSidebarCacheFiles();\r\n\r\n await writeFile(\r\n join(ARC_LIBRARY_PATH, \"StorableSidebar.json\"),\r\n JSON.stringify(json, null, 2)\r\n );\r\n}\r\n\r\nasync function mapJsonToSpaceNames(\r\n json: StorableSidebarJson\r\n): Promise {\r\n const itemsContainer = json.sidebar.containers.find((container) =>\r\n Object.hasOwnProperty.call(container, \"spaces\")\r\n );\r\n\r\n if (itemsContainer) {\r\n const spaces = itemsContainer.spaces as (string | SpaceData)[];\r\n\r\n return (\r\n spaces.filter((space) => typeof space !== \"string\") as SpaceData[]\r\n ).map((space) => space.title);\r\n }\r\n}\r\n\r\nasync function main(): Promise {\r\n await killArcProcess();\r\n await removeSidebarCacheFiles();\r\n\r\n const storableSidebarJson: StorableSidebarJson =\r\n await readStorableSidebarJson();\r\n\r\n const spaceNames = await mapJsonToSpaceNames(storableSidebarJson);\r\n\r\n const sourceSpaceName = await arg(\r\n \"Which Space theme you want to use for all the others?\",\r\n spaceNames\r\n );\r\n\r\n const sourceSpaceTheme: WindowTheme = await getSourceSpaceTheme(\r\n storableSidebarJson,\r\n sourceSpaceName\r\n );\r\n\r\n const targetSpaces: SpaceData[] = await getTargetSpaces(\r\n storableSidebarJson,\r\n sourceSpaceName\r\n );\r\n\r\n for (const targetSpace of targetSpaces) {\r\n targetSpace.customInfo.windowTheme = sourceSpaceTheme;\r\n }\r\n\r\n const targetSpacesSynced: SpaceModel[] = await getTargetSpacesSynced(\r\n storableSidebarJson,\r\n sourceSpaceName\r\n );\r\n\r\n for (const targetSpace of targetSpacesSynced) {\r\n targetSpace.value.customInfo.windowTheme = sourceSpaceTheme;\r\n }\r\n\r\n writeStorableSidebarJson(storableSidebarJson);\r\n}\r\n\r\nawait main();\r\n\r\n// Interfaces ------------------------------------------------------------------\r\n\r\ninterface Color {\r\n colorSpace: string;\r\n red: number;\r\n alpha: number;\r\n blue: number;\r\n green: number;\r\n}\r\n\r\ninterface ColorSettings {\r\n [key: string]: Color;\r\n}\r\n\r\ninterface WindowTheme {\r\n semanticColorPalette: {\r\n appearanceBased: {\r\n light: ColorSettings;\r\n dark: ColorSettings;\r\n };\r\n };\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface CustomInfo {\r\n windowTheme: WindowTheme;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SpaceData {\r\n title: string;\r\n customInfo: CustomInfo;\r\n id: string;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SpaceModel {\r\n value: SpaceData;\r\n [key: string]: unknown;\r\n}\r\n\r\ninterface SidebarSyncState {\r\n spaceModels: (string | SpaceModel)[];\r\n}\r\n\r\ninterface Sidebar {\r\n containers: Array<\r\n | {\r\n spaces: (SpaceData | string)[];\r\n }\r\n | {\r\n [key: string]: unknown;\r\n }\r\n >;\r\n}\r\n\r\ninterface StorableSidebarJson {\r\n sidebarSyncState: SidebarSyncState;\r\n sidebar: Sidebar;\r\n [key: string]: unknown;\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-10-04T09:19:35Z"},{"avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1345","url":"","title":"generate-password","command":"generate-password","content":"[`Generate-password`](https://www.npmjs.com/package/generate-password)s variants:\r\n\r\n\r\n\r\nCreate a [`~/.generatepasswordrc` file](https://www.npmjs.com/package/rc#standards) to change [defaults](https://www.npmjs.com/package/generate-password#user-content-available-options):\r\n\r\n```ini\r\nlength=12\r\nnumbers=true\r\nsymbols=true\r\n```\r\n\r\nSee source: https://gist.github.com/abernier/3dcf17422b23e151c2f60db874494233\r\n\r\n```ts\r\n// Generate password\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nimport { generate, generateMultiple, GenerateOptions } from \"generate-password\";\r\nimport rc from \"rc\";\r\n\r\nimport omit from \"lodash.omit\";\r\n\r\nimport { passwordStrength } from \"check-password-strength\";\r\n\r\nconst config = rc(\"generatepassword\", { length: 10 }); // create a ~/.generate-passwordrc file (see: https://www.npmjs.com/package/rc#standards)\r\n\r\nconst flags = {\r\n copy: {\r\n name: \"copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n};\r\n\r\nfunction formatVariant(o) {\r\n if (Object.keys(o).length === 0) o = config;\r\n\r\n const arr = [];\r\n Object.keys(o).forEach((k) => {\r\n if (o[k] === true) {\r\n arr.push(`--${k}`);\r\n } else if (o[k] === false) {\r\n arr.push(`--no-${k}`);\r\n }\r\n });\r\n return arr.join(\" \");\r\n}\r\n\r\nfunction sortedObj(o) {\r\n return Object.fromEntries(Object.entries(o).sort());\r\n}\r\n\r\nconst chosenPass = await arg(\r\n {\r\n placeholder: (config.length && String(config.length)) || undefined,\r\n description: `Length`,\r\n flags,\r\n },\r\n (input) => {\r\n const baseOpts = { ...config };\r\n\r\n const length = (input && Number(input)) || undefined;\r\n if (length) baseOpts.length = length;\r\n\r\n const variants = [\r\n {},\r\n { numbers: true, symbols: true },\r\n {\r\n numbers: true,\r\n symbols: true,\r\n excludeSimilarCharacters: true,\r\n },\r\n { numbers: true, symbols: true, lowercase: false },\r\n { numbers: true, symbols: true, uppercase: false },\r\n { numbers: true },\r\n { symbols: true },\r\n { lowercase: false },\r\n { uppercase: false },\r\n ];\r\n\r\n return variants.map((variant) => {\r\n const opts = { ...baseOpts, ...variant };\r\n const newPass = generate(opts);\r\n\r\n const description =\r\n Object.keys(variant).length === 0\r\n ? `from config: ${config.config}`\r\n : formatVariant(variant);\r\n\r\n return {\r\n name: `${newPass}`,\r\n description,\r\n preview() {\r\n return md(`${passwordStrength(newPass).value}\r\n\\`\\`\\`json\r\n${JSON.stringify(omit(sortedObj(opts), \"_\", \"configs\", \"config\"), null, 4)}\r\n\\`\\`\\`\r\nsee [available options](https://www.npmjs.com/package/generate-password#available-options)\r\n`);\r\n },\r\n value: newPass,\r\n };\r\n });\r\n }\r\n);\r\n\r\nif (flag?.copy) {\r\n copy(chosenPass);\r\n} else {\r\n setSelectedText(chosenPass);\r\n}\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-09-19T21:37:36Z"},{"name":"Generate Raycast Scripts","avatar":"https://avatars.githubusercontent.com/u/40895636?u=c291b499d0282201ff22db2e41d0b216c3bbece2&v=4","user":"cvbrian","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1342","url":"","title":"Trigger Scripts directly from Raycast","command":"trigger-scripts-directly-from-raycast","content":"Since I use Raycast as my launcher on my computer I wanted a way to trigger Script Kit scripts directly from the Raycast menu without a second step. I made a script to generate Raycast shell scripts that will directly trigger the Script Kit scripts. \r\n\r\n1. Make a directory to store the Raycast scripts.\r\n2. Add this directory to the Raycast settings as a script directory.\r\n3. Run this script and choose that directory when prompted. \r\n\r\nJust posting this as inspiration. This script is a little rough around the edges and could use refinement and customization to suit your needs. \r\n\r\n\r\n[Open generate-raycast-scripts in Script Kit](https://scriptkit.com/api/new?name=generate-raycast-scripts&url=https://gist.githubusercontent.com/cvbrian/b752361fb54b6efa813362087a72d330/raw/012f74a1249bf6a2406b02f0da1e07d6aa5d2e92/generate-raycast-scripts.js\")\r\n\r\n```js\r\n// Name: Generate Raycast Scripts\r\n\r\nimport \"@johnlindquist/kit\"\r\nconst collect = await npm(\"collect.js\")\r\n\r\n// get the directory to store the scripts in. Save in an environment variable\r\nconst directory = await env(\"RAYCAST_SCRIPTS_DIRECTORY\", async () => { return await selectFolder(\"Select a directory to store the scripts in\") })\r\n// get all the scripts from Script Kit\r\nlet scripts = collect(await getScripts())\r\n// remove preview from scripts\r\nscripts = scripts.map(script => {\r\n delete script.preview\r\n return script\r\n})\r\n// get only kenv scripts\r\nscripts = scripts.where('kenv', '')\r\n// TODO find out which scripts should be ignored\r\n// generate a script for each one in the directory\r\nscripts.each(async script => {\r\n const scriptContents = `#!/bin/bash\r\n\r\n# Required parameters:\r\n# @raycast.schemaVersion 1\r\n# @raycast.title ${script.name}\r\n# @raycast.mode silent\r\n# @raycast.packageName Script Kit Scripts\r\n# Documentation:\r\n# @raycast.description ${script.description}\r\n\r\n\r\n~/.kit/kar ${script.command}\r\n`\r\n await writeFile(`${directory}/${script.name}.sh`, scriptContents, \"utf-8\")\r\n})\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-09-19T16:27:55Z"},{"name":"Lorem ipsum","avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1340","url":"https://gist.githubusercontent.com/abernier/e93dd8f345cba61d4dab1dafb7282a45/raw/lorem-ipsum.ts","title":"lorem-ipsum-text","command":"lorem-ipsum-text","content":"[Open `lorem-ipsum` in Script Kit](https://scriptkit.com/api/new?name=lorem&url=https://gist.githubusercontent.com/abernier/e93dd8f345cba61d4dab1dafb7282a45/raw/lorem-ipsum.ts)\r\n\r\nhttps://github.com/johnlindquist/kit/assets/76580/a6e449aa-d9ef-4404-9be8-b5fd7855e6d0\r\n\r\n[source code](https://gist.github.com/abernier/e93dd8f345cba61d4dab1dafb7282a45#file-lorem-ipsum-ts)\r\n\r\n```ts\r\n// Name: Lorem ipsum\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nimport { loremIpsum, ILoremIpsumParams } from \"lorem-ipsum\";\r\n\r\nlet ret: ReturnType;\r\n\r\nconst DEFAULTS = {\r\n count: 1,\r\n};\r\n\r\nlet count: ILoremIpsumParams[\"count\"];\r\nlet units: ILoremIpsumParams[\"units\"];\r\n\r\nconst flags = {\r\n html: {\r\n name: \"html\",\r\n shortcut: \"cmd+h\",\r\n },\r\n copy: {\r\n name: \"copy\",\r\n shortcut: \"cmd+c\",\r\n },\r\n};\r\n\r\nfunction myLoremIpsum({ ...args }: Parameters[0] = {}) {\r\n const format = flag?.html ? \"html\" : \"plain\";\r\n\r\n // say(`generating ${count} ${units} of ${format} text`);\r\n return loremIpsum({ count, units, format, ...args });\r\n}\r\n\r\nawait arg(\r\n {\r\n placeholder: String(DEFAULTS.count),\r\n description: `Generate lorem ipsum text...`,\r\n flags,\r\n },\r\n (input) => {\r\n count = (input && Number(input)) || undefined;\r\n\r\n return [\"paragraphs\", \"sentences\", \"words\"].map((el) => ({\r\n name: el,\r\n preview: () => {\r\n units = el as ILoremIpsumParams[\"units\"];\r\n return myLoremIpsum();\r\n },\r\n }));\r\n }\r\n);\r\n\r\nconst loremText = myLoremIpsum();\r\n\r\nif (flag?.copy) {\r\n copy(loremText);\r\n} else {\r\n setSelectedText(loremText);\r\n}\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-09-17T12:50:07Z"},{"avatar":"https://avatars.githubusercontent.com/u/75037449?u=1fcae869eafe508a6cc283783373a405a2bb4a28&v=4","user":"sum117","author":"sum117","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1339","url":"","title":"Chunkify text into discord friendly chunks with an intuitive UI!","command":"chunkify-text-into-discord-friendly-chunks-with-an-intuitive-ui","content":"\r\n[Open chunkify-text in Script Kit](https://scriptkit.com/api/new?name=chunkify-text&url=https://gist.githubusercontent.com/sum117/423f829687bea3e32b8defbe0aa62731/raw/ed0d8bbe52749f4b583de1e8f265e06b8c77b654/chunkify-text.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\";\r\n\r\n/**\r\n * Chunkify a text into chunks of a given size.\r\n * @param {string} text\r\n * @param {number} chunkSize\r\n */\r\nfunction chunkify(text, chunkSize) {\r\n const chunks = [];\r\n\r\n let index = 0;\r\n while (index < text.length) {\r\n let end = index + chunkSize;\r\n\r\n while (end > index && text[end] !== \"\\n\") {\r\n end--;\r\n }\r\n\r\n if (end === index) {\r\n end = index + chunkSize;\r\n }\r\n\r\n const chunk = text.substring(index, end);\r\n chunks.push(chunk);\r\n index = end;\r\n }\r\n\r\n return chunks;\r\n}\r\n\r\nconst rawText = await editor(\r\n \"Paste the text to chunkify here (You can delete this placeholder).\"\r\n);\r\n\r\nlet textWidget = await widget(\r\n `\r\n \r\n
`;\r\n return { name, value, html };\r\n});\r\n\r\nlet flags = {\r\n view: {\r\n name: \"View in Shortcuts\",\r\n },\r\n run: {\r\n name: \"Run Shortcut\",\r\n },\r\n};\r\n\r\nlet shortcut = await arg(\r\n { prompt: \"Which shortcut would you like to run?\", flags },\r\n shortcuts\r\n);\r\nawait hide();\r\n\r\nif (flag?.view) {\r\n await exec(`/usr/bin/shortcuts view \"${shortcut.trim()}\"`);\r\n} else {\r\n let result = await exec(`/usr/bin/shortcuts run \"${shortcut.trim()}\" &`);\r\n if (result?.stdout) await div(md(`## Output:\\n\\n${result.stdout}`));\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-09-04T00:25:49Z"},{"name":"Filter Scripts","shortcut":"opt 7","cache":"true","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1331","url":"https://gist.githubusercontent.com/johnlindquist/cc108bf018b67a0116e1b8dde95c7280/raw/070bda9b7e16cc9d56eeff330961fdc1df84ed73/filter-scripts.ts","title":"List Scripts of Specific Kenv","command":"list-scripts-of-specific-kenv","content":"\r\n[Open filter-scripts in Script Kit](https://scriptkit.com/api/new?name=filter-scripts&url=https://gist.githubusercontent.com/johnlindquist/cc108bf018b67a0116e1b8dde95c7280/raw/070bda9b7e16cc9d56eeff330961fdc1df84ed73/filter-scripts.ts\")\r\n\r\n```js\r\n// Name: Filter Scripts\r\n// Shortcut: opt 7\r\n// Cache: true\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet kenv = \"kit-examples\"\r\n\r\nlet scripts = (await getScripts()).filter(script => script.kenv === kenv)\r\n\r\nlet script = await arg(\"Run Script\", scripts)\r\n\r\nawait run(script.filePath)\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-08-20T19:22:54Z"},{"name":"Expand TS","author":"Nauris Pūķis","twitter":"pyronaur","shortcut":"cmd+shift+e","avatar":"https://avatars.githubusercontent.com/u/988095?u=d3a3cc84565dadde5ad7120646533ec951b2ab20&v=4","user":"pyronaur","discussion":"https://github.com/johnlindquist/kit/discussions/1330","url":"","title":"Snippets on Steroids","command":"snippets-on-steroids","content":"**Summary**\r\nScriptKit now has snippets built-in, but they're lacking a couple of powerful features at the moment.\r\n\r\nSo I built this addon to handle the selection, cursor, clipboard, and `evil` script `eval()`:\r\n\r\n\r\nHere's an example snippet that's going to replace the selection with a script tag and set variables based on clipboard and ScriptKit input:\r\n```ts\r\n// Name: Expand TS\r\n\r\n```\r\n\r\nIf you like the script, [retweet it](https://twitter.com/pyronaur/status/1693219818843918359) 😇\r\n\r\n## Script:\r\n\r\n```ts\r\n// Name: Snippets on Steroids\r\n// Author: pyronaur\r\n// Twitter: @pyronaur\r\n// Shortcut: cmd+shift+e\r\n/**\r\n * This script expands a given snippet and replaces placeholders with their respective values.\r\n * \r\n * ## Placeholders\r\n * - $CURSOR$ - set the cursor position in the snippet after it's expanded.\r\n * - $SELECTION$ - insert the currently selected text within the snippet.\r\n * - $CLIPBOARD$ - insert the current clipboard text within the snippet.\r\n * \r\n * ## Code Evaluation\r\n * You can place any JavaScript code within $$...$$ and it will be evaluated and replaced with the result.\r\n * \r\n * For example:\r\n * The code inside the $$...$$ will be executed and the result will replace the placeholder.\r\n * Example:\r\n * ```\r\n * const clipboard = '$$clipboard.readText()$$';\r\n * const date = '$$new Date().toLocaleDateString()$$';\r\n * const number = '$$arg(\"What is the magic number?\", ['42', '7', '8'])$$';\r\n * ```\r\n * Note:\r\n * The script execution is potentially dangerous and should be enabled with caution.\r\n * You have to enable it by setting the `I_AM_THE_DANGER` variable to `true`.\r\n */\r\nimport \"@johnlindquist/kit\"\r\nimport { Choice } from '@johnlindquist/kit';\r\n\r\nconst { globby } = await npm(\"globby\");\r\nconst snippet_path = kenvPath('snippets');\r\n\r\n// 🔴 DANGER 🔴\r\n// SETTING THIS TO TRUE ALLOW ANY SCRIPT TO BE EXECUTED BY SNIPPETS\r\nconst I_AM_THE_DANGER = false;\r\n\r\nasync function dangerous_evil_parse(content: string) {\r\n\tconst evil_regex = /\\$\\$(.*?)\\$\\$/g;\r\n\tlet match;\r\n\tlet matches = [];\r\n\twhile ((match = evil_regex.exec(content)) !== null) {\r\n\t\tmatches.push(match);\r\n\t}\r\n\tfor (let match of matches) {\r\n\t\tconst script = match[1];\r\n\t\tconst result = I_AM_THE_DANGER ? await eval(script) : ''; // 🎩 😎\r\n\t\tcontent = content.replace(`$$${script}$$`, result);\r\n\t}\r\n\r\n\treturn content;\r\n}\r\n\r\n// Find $CURSOR$ and set cursor position\r\nasync function set_with_cursor(content: string) {\r\n\tconst cursor_index = content.indexOf('$CURSOR$');\r\n\tif (cursor_index === -1) {\r\n\t\treturn false;\r\n\t}\r\n\t// Remove $$CURSOR$$\r\n\tcontent = content.replace('$CURSOR$', '');\r\n\tawait setSelectedText(content);\r\n\t// There's some async weirdness here\r\n\t// so we'll just wait 100ms\r\n\tawait new Promise(resolve => setTimeout(resolve, 100));\r\n\tconst target_cursor_position = content.length - cursor_index;\r\n\tconst keystrokes = [];\r\n\tfor (let i = 0; i < target_cursor_position; i++) {\r\n\t\tkeystrokes.push(keystroke('left'));\r\n\t}\r\n\tawait Promise.all(keystrokes);\r\n\treturn true;\r\n}\r\n\r\nfunction files_to_choices(files: string[]): Choice[] {\r\n\treturn files.map(file => ({\r\n\t\tname: path.basename(file, '').split('.')[0],\r\n\t\tvalue: file\r\n\t}));\r\n}\r\n\r\nasync function get_snippet_files() {\r\n\t// Untested attempt to fix windows paths (I don't have a windows machine)\r\n\tconst snippet_files = await globby(`${snippet_path}/*`);\r\n\tif (process.platform == 'win32') {\r\n\t\treturn snippet_files;\r\n\t}\r\n\treturn snippet_files.map(file => file.replace(/\\\\/g, '/'));\r\n}\r\n\r\nasync function get_content(snippet: string) {\r\n\tconst snippet_content = await readFile(snippet, 'utf8');\r\n\tconst snippet_lines = snippet_content.split('\\n')\r\n\r\n\t// Remove comments and empty lines until first line of snippet\r\n\treturn snippet_lines\r\n\t\t.slice(snippet_lines.findIndex(line => !line.startsWith('//')))\r\n\t\t.join('\\n');\r\n}\r\n\r\nasync function insert_selection(content: string) {\r\n\tif (content.includes('$SELECTION$') === false) {\r\n\t\treturn content;\r\n\t}\r\n\tconst selection = await getSelectedText();\r\n\tcontent = content.replace('$SELECTION$', selection);\r\n\treturn content;\r\n}\r\n\r\nasync function insert_clipboard(content: string) {\r\n\tif (content.includes('$CLIPBOARD$') === false) {\r\n\t\treturn content;\r\n\t}\r\n\tconst text = await clipboard.readText();\r\n\tcontent = content.replace('$CLIPBOARD$', text);\r\n\treturn content;\r\n}\r\n\r\n\r\n\r\n// 🚀 Go!\r\nconst snippet_file = await arg(\"Which snippet?\", files_to_choices(await get_snippet_files()));\r\nlet content = await get_content(snippet_file);\r\ncontent = await insert_selection(content);\r\ncontent = await insert_clipboard(content);\r\ncontent = await dangerous_evil_parse(content);\r\nif (!(await set_with_cursor(content))) {\r\n\tawait setSelectedText(content);\r\n}\r\n```\r\n\r\nProps @johnlindquist for helpful pointers 👍","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-08-20T09:43:40Z"},{"avatar":"https://avatars.githubusercontent.com/u/91077547?u=912b5cbbf037a7082ac71e829d74641975d545ff&v=4","user":"MartinLednar","author":"Martin Lednár","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1327","url":"","title":"Jira monthly time logger","command":"jira-monthly-time-logger","content":"# About\r\n\r\nLog tasks you worked on and the total hours you worked for current month into jira.\r\n\r\n- [Gist link](https://gist.githubusercontent.com/MartinLednar/3e3b0e8b23c92734bf946662a4f4b502/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n- [Download link](https://scriptkit.com/api/new?name=jira-monthly-time-logger&url=https://gist.githubusercontent.com/MartinLednar/d702e7993415b04f9e85fd29b910e0cd/raw/c3930a933abe4760a11a555a12db4eb432ebc231/jira-monthly-time-logger.ts)\r\n\r\n\r\n\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-08-09T13:12:04Z"},{"name":"tmux sesh","description":"","author":"Lazar Nikolov","twitter":"NikolovLazar","avatar":"https://avatars.githubusercontent.com/u/5396211?u=3f22a05cb9cf25b3b6c9f8b75f4554249487a254&v=4","user":"nikolovlazar","discussion":"https://github.com/johnlindquist/kit/discussions/1326","url":"","title":"Attach to tmux session with Kitty terminal","command":"attach-to-tmux-session-with-kitty-terminal","content":"### Prerequisites:\r\n1. Install [Kitty](https://sw.kovidgoyal.net/kitty/)\r\n2. Add `kitty` to `PATH`: `sudo ln -s /Applications/kitty.app/Contents/MacOS/kitty /usr/local/bin/kitty` (assuming `/usr/local/bin` is in your `PATH`)\r\n3. Kit: `Sync $PATH from Terminal to Kit.app`\r\n\r\n\r\n\r\n\r\n\r\n[Open tmux-sesh in Script Kit](https://scriptkit.com/api/new?name=tmux-sesh&url=https://gist.githubusercontent.com/nikolovlazar/21a78f492e117a5e0dca1685cb668f4d/raw/94e44f60ef8807250f365b996b610d8bb05fd311/tmux-sesh.js\")\r\n\r\n```js\r\n// Name: tmux sesh\r\n// Description: Attach to a tmux session\r\n// Author: Lazar Nikolov\r\n// Twitter: @NikolovLazar\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst sessionsCmd = await $`tmux list-sessions`;\r\n\r\nlet sessions = sessionsCmd.stdout\r\n .split('\\n')\r\n .map((line) => line.split(':')[0])\r\n .filter((sesh) => !!sesh);\r\n\r\nlet choice = await arg('Attach to session:', sessions);\r\n\r\nawait $`kitty --hold sh -c \"tmux a -t ${choice}\"`;\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-08-08T14:35:41Z"},{"name":"Reveal password","shortcut":"cmd *","avatar":"https://avatars.githubusercontent.com/u/76580?v=4","user":"abernier","author":"Antoine BERNIER","twitter":"abernier_","discussion":"https://github.com/johnlindquist/kit/discussions/1322","url":"https://gist.githubusercontent.com/abernier/582e1458195ec34268305298e4b3b86b/raw/reveal-password.%25E2%2596%25B6.ts","title":"Reveal password","command":"reveal-password","content":"[Open `reveal-password` in Script Kit](https://scriptkit.com/api/new?name=revealpassword&url=https://gist.githubusercontent.com/abernier/582e1458195ec34268305298e4b3b86b/raw/reveal-password.%25E2%2596%25B6.ts)\r\n\r\n`| ******* |` cmd * → `| toto123 |`\r\n\r\n```ts\r\n// Name: Reveal password\r\n// Shortcut: cmd *\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet js = `\r\ndocument.activeElement.type = document.activeElement.type === 'password' ? 'text' : 'password';\r\n`;\r\n\r\nlet value = await applescript(`\r\ntell application \"Google Chrome\" to tell window 1\r\n\tget execute active tab javascript \"\r\n\r\n${js}\r\n\r\n\"\r\nend tell\r\n`);\r\n```\r\n-- https://gist.github.com/abernier/582e1458195ec34268305298e4b3b86b","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-31T06:08:58Z"},{"menu":"Gather Guest List","description":"","author":"Kent C. Dodds","twitter":"kentcdodds","avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1310","url":"https://gist.githubusercontent.com/kentcdodds/592bd3aebb51971c3a968954ede061f6/raw/5bc5d065782e50f269c5ed9090976722fae50140/gather-guest.ts","title":"Gather Town Guest Management","command":"gather-town-guest-management","content":"I use this script to manage who has access to join my [Gather.town](https://gather.town) space, so I don't have to manually approve folks joining, they don't need me to be there to join, and they can only access it if they're logged in and are on the list (they've purchased a ticket).\r\n\r\nVery cool thing I can throw together to solve my problems in an hour.\r\n\r\nFind the most up-to-date version in my repo: https://github.com/kentcdodds/.kenv/blob/main/scripts/gather-guest.ts\r\n\r\n\r\n[Open gather-guest in Script Kit](https://scriptkit.com/api/new?name=gather-guest&url=https://gist.githubusercontent.com/kentcdodds/592bd3aebb51971c3a968954ede061f6/raw/5bc5d065782e50f269c5ed9090976722fae50140/gather-guest.ts\")\r\n\r\n```js\r\n// Menu: Gather Guest List\r\n// Description: Handle the Guest List for Gather\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport {z} from 'zod'\r\n\r\nconst GuestObjectSchema = z.object({\r\n name: z.string().optional(),\r\n affiliation: z.string().optional(),\r\n role: z.string().optional(),\r\n})\r\nconst GuestsSchema = z.record(z.string().email(), GuestObjectSchema)\r\n\r\nconst GATHER_API_KEY = await env('GATHER_API_KEY', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_API_KEY',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Get a Gather API Key\r\n\r\n[app.gather.town/apikeys](https://app.gather.town/apikeys)\r\n `),\r\n )\r\n})\r\n\r\nconst GATHER_SPACE_ID = await env('GATHER_SPACE_ID', async () => {\r\n return await arg(\r\n {\r\n placeholder: 'GATHER_SPACE_ID',\r\n ignoreBlur: true,\r\n },\r\n () =>\r\n md(`\r\n# Specify the Gather Space ID\r\n\r\nIt's everything after \"app/\" in this URL with \"/\" replaced by \"\\\\\":\r\n\r\nhttps://app.gather.town/app/BL0B93FK23T/example\r\n `),\r\n )\r\n})\r\n\r\nasync function go() {\r\n const params = new URLSearchParams({\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n })\r\n const rawGuests = await fetch(\r\n `https://gather.town/api/getEmailGuestlist?${params}`,\r\n ).then(r => r.json())\r\n\r\n const guests = GuestsSchema.parse(rawGuests)\r\n const choices = [\r\n {name: '➕ Add a guest', value: {type: 'add-guest'}},\r\n ...Object.entries(guests).map(([email, {name, affiliation, role}]) => ({\r\n name: `${email} (${name?.trim() || 'Unnamed'}, ${\r\n affiliation?.trim() || 'Unaffiliated'\r\n }, ${role?.trim() || 'No role'})`,\r\n value: {type: 'modify-guest', email},\r\n })),\r\n ]\r\n const rawSelection = await arg(\r\n {placeholder: 'Which guest would you like to modify?'},\r\n choices,\r\n )\r\n const SelectionSchema = z.union([\r\n z.object({\r\n type: z.literal('add-guest'),\r\n }),\r\n z.object({\r\n type: z.literal('modify-guest'),\r\n email: z.string(),\r\n }),\r\n ])\r\n const selection = SelectionSchema.parse(rawSelection)\r\n switch (selection.type) {\r\n case 'add-guest': {\r\n await addGuest()\r\n return go()\r\n }\r\n case 'modify-guest': {\r\n await modifyGuest(selection.email, guests)\r\n return go()\r\n }\r\n }\r\n}\r\n\r\nasync function addGuest() {\r\n const email = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: `What's the guests' email?`}))\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: {[email]: {}},\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\nasync function modifyGuest(\r\n email: string,\r\n guests: z.infer,\r\n) {\r\n const guest = guests[email]\r\n const action = await arg({placeholder: `What would you like to do?`}, [\r\n {name: 'Remove Guest', value: 'remove'},\r\n {name: `Change Guest Email (${email})`, value: 'change-email'},\r\n {\r\n name: `Change Guest Name (${guest.name?.trim() || 'Unnamed'})`,\r\n value: 'change-name',\r\n },\r\n {\r\n name: `Change Guest Affiliation (${\r\n guest.affiliation?.trim() || 'Unaffiliated'\r\n })`,\r\n value: 'change-affiliation',\r\n },\r\n {\r\n name: `Change Guest Role (${guest.role?.trim() || 'No role'})`,\r\n value: 'change-role',\r\n },\r\n {\r\n name: `Cancel`,\r\n value: 'cancel',\r\n },\r\n ])\r\n switch (action) {\r\n case 'remove': {\r\n delete guests[email]\r\n break\r\n }\r\n case 'change-email': {\r\n const newEmail = z\r\n .string()\r\n .email()\r\n .parse(await arg({placeholder: 'New Email'}))\r\n guests[newEmail] = guests[email]\r\n delete guests[email]\r\n email = newEmail\r\n break\r\n }\r\n case 'change-name': {\r\n const newName = await arg({placeholder: 'New Name'})\r\n if (newName) {\r\n guests[email].name = newName\r\n } else {\r\n delete guests[email].name\r\n }\r\n break\r\n }\r\n case 'change-affiliation': {\r\n const newAffiliation = await arg({\r\n placeholder: 'New Affiliation',\r\n })\r\n if (newAffiliation) {\r\n guests[email].affiliation = newAffiliation\r\n } else {\r\n delete guests[email].affiliation\r\n }\r\n break\r\n }\r\n case 'change-role': {\r\n const newRole = await arg({placeholder: 'New Role'})\r\n if (newRole) {\r\n guests[email].role = newRole\r\n } else {\r\n delete guests[email].role\r\n }\r\n break\r\n }\r\n case 'cancel': {\r\n return go()\r\n }\r\n }\r\n const body = {\r\n apiKey: GATHER_API_KEY,\r\n spaceId: GATHER_SPACE_ID,\r\n guestlist: guests,\r\n overwrite: true,\r\n }\r\n const updateResponse = await fetch(\r\n 'https://api.gather.town/api/setEmailGuestlist',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(body),\r\n headers: {\r\n 'content-type': 'application/json',\r\n },\r\n },\r\n )\r\n const update = await updateResponse.json()\r\n console.log('Guest Update: ', update[email])\r\n}\r\n\r\ngo()\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-13T19:05:44Z"},{"name":"Correct selection","description":"","author":"Ivan Rybnikov","twitter":null,"shortcut":"cmd option g","avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","discussion":"https://github.com/johnlindquist/kit/discussions/1309","url":"","title":"Correct selection with ChatGPT","command":"correct-selection-with-chatgpt","content":"[Open Correct selection with ChatGPT in Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/646da11d9a5dbb2151a2053c4d510dd0/raw/8f9b16dd5c636ef6192c12b037ad80f7d84d0193/correct-selection-script.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-13T12:30:37Z"},{"name":"tiktok-images","description":"","author":"Trevor Atlas","twitter":"trevoratlas","threads":"trevor.atlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1308","url":"https://gist.githubusercontent.com/trevor-atlas/9bc38697613660a228d89f45c5d5ead9/raw/cdbfee11f395fdf8fdaedacfafbb555a1db9bd65/tiktok-images.ts","title":"Resize and composite images for tiktok","command":"resize-and-composite-images-for-tiktok","content":"\r\n[Open tiktok-images in Script Kit](https://scriptkit.com/api/new?name=tiktok-images&url=https://gist.githubusercontent.com/trevor-atlas/9bc38697613660a228d89f45c5d5ead9/raw/cdbfee11f395fdf8fdaedacfafbb555a1db9bd65/tiktok-images.ts\")\r\n\r\n```js\r\n// Name: tiktok-images\r\n// Description: Resize images to fit TikTok's 9:16 aspect ratio and avoid being covered by the UI\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Threads: trevor.atlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst sharp = await npm('sharp');\r\nconst { getAverageColor } = await npm('fast-average-color-node');\r\n\r\nconst width = 1440;\r\nconst height = 2400;\r\nconst density = 72;\r\nconst scale = .8;\r\nconst validTypes = new Set(['image/png', 'image/jpeg', 'image/jpg']);\r\nconst outputPath = path.join(home(), 'Desktop', 'resized-images');\r\n\r\nasync function processImage(imageFilepath: string) {\r\n try {\r\n const averageColor = await getAverageColor(imageFilepath);\r\n const image = await sharp(imageFilepath)\r\n .withMetadata({ density })\r\n .resize({ fit: 'inside', width: Math.floor(width * scale), height: Math.floor(height * scale) })\r\n .png({ quality: 100 })\r\n\r\n .toBuffer();\r\n\r\n const color = averageColor.hex || 'black';\r\n\r\n // Add a matching background\r\n const background = await sharp({\r\n create: {\r\n channels: 4,\r\n background: color,\r\n width,\r\n height,\r\n },\r\n })\r\n .withMetadata({ density })\r\n .png({ quality: 100})\r\n .toBuffer();\r\n\r\n\r\n const res = await sharp(background)\r\n .composite([{ input: image, gravity: 'centre' }])\r\n .png({ quality: 100 })\r\n .toBuffer();\r\n\r\n return res;\r\n } catch (error) {\r\n console.error(error);\r\n throw error;\r\n }\r\n};\r\n\r\ninterface FileInfo {\r\n lastModified: number;\r\n lastModifiedDate: string;//\"2023-07-12T17:35:13.573Z\"\r\n name: string;\r\n path: string;//\"/Users/uname/Desktop/screenshots/Screenshot 2022-01-12 at 1.35.08 PM.png\"\r\n size: number;\r\n type: string;//\"image/png\"\r\n webkitRelativePath: string;\r\n}\r\n\r\n\r\ntry {\r\n const fileInfos: FileInfo[] = await drop('Drop images to resize');\r\n const imagePaths = fileInfos\r\n .filter(({type}) => validTypes.has(type))\r\n .map(fileInfo => fileInfo.path);\r\n\r\n if (!imagePaths.length) {\r\n await notify('No valid images found. Supports .png, .jpg, and .jpeg');\r\n exit();\r\n }\r\n\r\n await ensureDir(outputPath);\r\n\r\n for (const imagePath of imagePaths) {\r\n const image = await processImage(imagePath);\r\n const [filename] = path.basename(imagePath).split('.');\r\n const finalPath = path.join(outputPath, `${filename}-processed.png`);\r\n await writeFile(finalPath, image);\r\n console.log(`Resized ${finalPath}`);\r\n }\r\n\r\n await notify('Image(s) resized');\r\n} catch (error) {\r\n console.error(error);\r\n await notify('Error resizing images. Check the log for details.');\r\n}\r\n\r\nawait open(outputPath);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-12T22:37:36Z"},{"name":"Correct selection","description":"","author":"Ivan Rybnikov","twitter":null,"shortcut":"cmd option g","avatar":"https://avatars.githubusercontent.com/u/13164347?u=375e8ca0cf813e284505f7b31f4c91d6bc6d2985&v=4","user":"ivryb","discussion":"https://github.com/johnlindquist/kit/discussions/1307","url":"","title":"Correct selection with ChatGPT","command":"correct-selection-with-chatgpt","content":"[Install \"Correct selection with ChatGPT\" to Script Kit](https://scriptkit.com/api/new?name=Correct%20selection%20with%20ChatGPT&url=https://gist.githubusercontent.com/ivryb/9f63a1881b1827773682cdf7e404b05c/raw/9bce6cdf6dc54497f6d7020807fc1cc6bf405131/correct-selection.js)\r\n\r\n```js\r\n/*\r\n# Correct selection with ChatGPT\r\n\r\nFix grammar and spelling mistakes in any text field.\r\n\r\nHighlight some text and press `cmd+option+g` to send it through ChatGPT to replace the text response.\r\n*/\r\n\r\n// Name: Correct selection\r\n// Description: Fix grammar and spelling mistakes in any text field.\r\n// Author: Evan Fisher\r\n// Twitter: @ivryb\r\n// Shortcut: cmd option g\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nimport Bottleneck from 'bottleneck';\r\n\r\nimport { createChat } from 'completions';\r\n\r\nconst openAiKey = await env('OPENAI_API_KEY', {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst chat = createChat({\r\n apiKey: openAiKey,\r\n model: 'gpt-3.5-turbo',\r\n});\r\n\r\nconst correctionPrompt = (text) =>\r\n `Please fix the grammar and spelling of the following text and return it without any other changes: ###${text}###`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst text = await getSelectedText();\r\n\r\nif (text) {\r\n await chat.sendMessage(correctionPrompt(text), {\r\n onUpdate: async ({ message }) => {\r\n const content = message.choices[0]?.delta?.content;\r\n\r\n if (content) {\r\n wrappedType(content);\r\n }\r\n },\r\n });\r\n}\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-12T17:02:06Z"},{"name":"Decode Base64","description":"","author":null,"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1303","url":"","title":"Decode a base64 string","command":"decode-a-base64-string","content":"Everything is in the title of this post, this script will allow you to decode a base64 string.\r\n\r\n[Open base64-decode in Script Kit](https://scriptkit.com/api/new?name=base64-decode&url=https://gist.githubusercontent.com/ElTacitos/8eaa571a6026383c9ce71e593e31b598/raw/6aa4a043a4458b980111bfb76ebe0b435d90a524/base64-decode.js\")\r\n\r\n```js\r\n// Name: Decode Base64\r\n// Description: Decode a base64 string and copy it to the clipboard\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet base64 = await arg(\"Enter base64 string to decode\")\r\nlet decoded = atob(base64)\r\n\r\nawait clipboard.writeText(decoded)\r\nawait div(md(`\r\n# ${decoded}\r\n`))\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-07-10T10:09:08Z"},{"menu":"De-Acronym-ify","description":"","author":"Trevor Atlas","twitter":"trevoratlas","shortcut":"cmd ctrl opt shift a","group":"work","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1296","url":"https://gist.githubusercontent.com/trevor-atlas/992682a54fa4ec44ccc8cc58e889e026/raw/f4ec3016bd7f8b1af4be65a64f3d500c19231e71/de-acronym.ts","title":"Replace user-defined acronyms with the full text","command":"replace-user-defined-acronyms-with-the-full-text","content":"\r\n[Open de-acronym in Script Kit](https://scriptkit.com/api/new?name=de-acronym&url=https://gist.githubusercontent.com/trevor-atlas/992682a54fa4ec44ccc8cc58e889e026/raw/f4ec3016bd7f8b1af4be65a64f3d500c19231e71/de-acronym.ts\")\r\n\r\n```js\r\n// Menu: De-Acronym-ify\r\n// Description: Replace acronyms with their full names\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Shortcut: cmd ctrl opt shift a\r\n// Group: work\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nlet text = '';\r\nconst clipboardValue = await paste();\r\nconst selection = await getSelectedText();\r\n\r\nif (selection) {\r\n text = selection;\r\n console.log('use selection', selection);\r\n}\r\n\r\nif (clipboardValue && !selection) {\r\n text = clipboardValue;\r\n console.log('use clipboard', text);\r\n}\r\n\r\nif (!text) {\r\n text = await arg('Enter text to de-acronym-ify');\r\n console.log('use prompt', text);\r\n}\r\n\r\nconst acronyms: Array<[string | RegExp, string]> = [\r\n ['PD', 'Product Design'],\r\n ['PM', 'Product Management'],\r\n ['JS', 'JavaScript'],\r\n ['TS', 'TypeScript'],\r\n];\r\n\r\nconst result = acronyms.reduce(\r\n (acc, [acronym, expansion]) => acc.replace(acronym, expansion),\r\n text\r\n);\r\n\r\nif (!selection) {\r\n copy(result);\r\n} else {\r\n await setSelectedText(result);\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-30T20:35:34Z"},{"name":"humanlike typing","description":"","author":"Trevor Atlas","twitter":"trevoratlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1295","url":"https://gist.githubusercontent.com/trevor-atlas/17746a243dd9bbfa8062d8fb86b5fc20/raw/45b2a5cb769b76db73f70579edf28b469ba194bd/humanlike-typing.ts","title":"Type clipboard like a human","command":"type-clipboard-like-a-human","content":"I haven't quite dialed in the random delays as well as I'd like, but it gets the job done :]\r\n\r\n[Open humanlike-typing in Script Kit](https://scriptkit.com/api/new?name=humanlike-typing&url=https://gist.githubusercontent.com/trevor-atlas/17746a243dd9bbfa8062d8fb86b5fc20/raw/45b2a5cb769b76db73f70579edf28b469ba194bd/humanlike-typing.ts\")\r\n\r\n```js\r\n// Name: humanlike typing\r\n// Description: Type the contents of your clipboard as if you were a human\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\n\r\nawait applescript(String.raw`\r\nset texttowrite to the clipboard as text\r\ntell application \"System Events\"\r\n repeat with i from 1 to count characters of texttowrite\r\n if (character i of texttowrite) is equal to linefeed or (character i of texttowrite) is equal to return & linefeed or (character i of texttowrite) is equal to return then\r\n keystroke return\r\n else\r\n keystroke (character i of texttowrite)\r\n end\r\n if (character i of texttowrite) is equal to \" \" then\r\n delay (random number from 0.01 to 0.1)\r\n else if (character i of texttowrite) is equal to \"\\n\" then\r\n delay (random number from 0.1 to 0.3)\r\n else\r\n delay (random number from 0.01 to 0.05)\r\n end\r\n end repeat\r\nend tell\r\n`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-30T20:32:02Z"},{"name":"vpn","author":"Trevor Atlas","twitter":"trevoratlas","schedule":"*/15 * * * *","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1294","url":"https://gist.githubusercontent.com/trevor-atlas/45ea4ba63553e81facc93105cf52dc65/raw/a983e86ac4885afeff3928e268ad780020beffda/vpn.ts","title":"Connect to GlobalProtect VPN if not connected","command":"connect-to-globalprotect-vpn-if-not-connected","content":"\r\n[Open vpn in Script Kit](https://scriptkit.com/api/new?name=vpn&url=https://gist.githubusercontent.com/trevor-atlas/45ea4ba63553e81facc93105cf52dc65/raw/a983e86ac4885afeff3928e268ad780020beffda/vpn.ts\")\r\n\r\n```js\r\n// Name: vpn\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// Schedule: */15 * * * *\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\napplescript(`\r\ntell application \"System Events\" to tell process \"GlobalProtect\"\r\n\tset connectionStatus to get help of every menu bar item of menu bar 2\r\n\tif item 1 of connectionStatus = \"Not Connected\" then\r\n\t\tclick menu bar item 1 of menu bar 2 -- Activates the GlobalProtect \"window\" in the menubar\r\n\t\ttry\r\n\t\t\tclick button \"Connect\" of window 1\r\n\t\tend try\r\n\t\tclick menu bar item 1 of menu bar 2 -- This will close the GlobalProtect \"window\" after clicking Connect/Disconnect. This is optional.\r\n\tend if\r\nend tell\r\n`);\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-30T19:36:57Z"},{"name":"Search Open PRs","description":"","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1284","url":"","title":"Search Open PRs","command":"search-open-prs","content":"Change the owner and repo name to desired repo and get to reviewing!\r\n\r\n[Open search-open-pr in Script Kit](https://scriptkit.com/api/new?name=search-open-pr&url=https://gist.githubusercontent.com/mabry1985/7cf5cec8d5913948aeda070f51ecfe4d/raw/64c98abcade33cdeb54604f29a06c55f05d374f1/search-open-pr.js\")\r\n\r\n```js\r\n// Name: Search Open PRs\r\n// Description: Search open PRs in a repo\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst fetch = await npm(\"node-fetch\");\r\nconst variables = {\r\n owner: \"knapsack-labs\",\r\n repoName: \"app-monorepo\",\r\n};\r\n\r\nlet token = await env(\"GITHUB_AUTH_TOKEN\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst query = `\r\nquery getPrs($owner: String!, $repoName: String!) {\r\n repository(owner: $owner, name: $repoName) {\r\n pullRequests(last: 100, states: OPEN) {\r\n nodes {\r\n body\r\n createdAt\r\n mergeable\r\n number\r\n state\r\n title\r\n updatedAt\r\n url\r\n author {\r\n avatarUrl\r\n login\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n`;\r\n\r\nasync function getPrs() {\r\n return fetch(\"https://api.github.com/graphql\", {\r\n headers: {\r\n authorization: `bearer ${token}`,\r\n },\r\n method: \"POST\",\r\n body: JSON.stringify({ query, variables }),\r\n })\r\n .then((res) => res.json())\r\n .catch((err) => {\r\n console.log(err);\r\n exit();\r\n });\r\n}\r\nconst prs = await getPrs();\r\nconst openPRs = prs.data.repository.pullRequests.nodes;\r\nconst sortedPrs = openPRs.sort(\r\n (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)\r\n);\r\nconst pr = await arg(\r\n {\r\n placeholder: `Select a PR to view`,\r\n },\r\n sortedPrs.map((pr) => {\r\n return {\r\n name: `${pr.number} - ${pr.title}`,\r\n preview: () =>\r\n `
\r\n
${pr.number} - ${pr.title}
\r\n \r\n
Ready to Merge: ${pr.mergeable === \"MERGEABLE\" ? \"✅\" : \"⛔\"}
\r\n
${md(pr.body)}
\r\n \r\n
Author: ${pr.author.login}
\r\n \r\n \r\n
`,\r\n value: pr.number,\r\n };\r\n })\r\n);\r\n\r\nconst prInfo = sortedPrs.find((p) => p.number === pr);\r\nbrowse(prInfo.url);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-21T20:59:28Z"},{"name":"JS Expression","description":"","avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1283","url":"","title":"A simple calculator using js expressions","command":"a-simple-calculator-using-js-expressions","content":"\r\n[Open js-expression in Script Kit](https://scriptkit.com/api/new?name=js-expression&url=https://gist.githubusercontent.com/alkene0005/25bd8b0b560fdc4d1b582bf1c6a4ed55/raw/af15c0765269eb22b6aaa7ac207ed152c0088d3c/js-expression.js\")\r\n\r\n```js\r\n// Name: JS Expression\r\n// Description: I prefer to define it as a simple calculator\r\n\r\n// Global Objects\r\nlet arr = [1, 2, 3, 4, 5]\r\nlet obj = {name: 'Mike', age: 20}\r\n\r\n// Global Functions\r\nlet {\r\n ceil, floor, round, trunc, abs, PI,\r\n sin, cos, tan, log, log2, log10, exp, sqrt, cbrt, pow\r\n} = Math\r\n\r\n// Factorial\r\nlet fact = num => _.reduce(_.range(1, num + 1), (acc, i) => acc * i, 1)\r\n\r\nlet selected = await arg({\r\n placeholder: 'Expression ...',\r\n enter: 'Copy & Exit',\r\n shortcuts: [{\r\n name: 'Repalce', key: 'cmd+r', bar: 'right', onPress: async (input, {focused}) => {\r\n setInput(evalExp(input))\r\n }\r\n }]\r\n}, async (input) => {\r\n let res = input ? evalExp(input) : ''\r\n return md(`~~~javascript\\n${res}\\n~~~`)\r\n})\r\n\r\nif (selected) await copy(evalExp(selected))\r\n\r\nfunction evalExp(input) {\r\n let value = eval(`(${input})`)\r\n if (typeof value == 'number') return (value % 1 != 0 ? value.toFixed(2) : value) + ''\r\n if (typeof value == 'array') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'object') return JSON.stringify(value, null, 2)\r\n if (typeof value == 'function') return ''\r\n}\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-18T17:37:10Z"},{"name":"Steam","avatar":"https://avatars.githubusercontent.com/u/61105068?u=f283e5ba5e98aeae47c6b325ba9827ecacf24dd0&v=4","user":"alkene0005","author":"Alken E","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1281","url":"","title":"Quick Search Steam Game","command":"quick-search-steam-game","content":"[Install](https://scriptkit.com/api/new?name=steam-game-search&url=https://gist.githubusercontent.com/alkene0005/6dc59b6596a258f63cb774dd1206eb79/raw/c3a68d5fb11f78455efbfceebe70224bd1fc98f6/steam-game-search.js)\r\n\r\n```js\r\n// Name: Steam\r\n\r\nimport axios from 'axios'\r\nimport cheerio from 'cheerio'\r\n\r\n// Language-dependent configuration\r\nconst cc = 'US'\r\nconst l = 'english'\r\n\r\nfunction buildResult(value, image, title) {\r\n return {\r\n name: 'abc',\r\n value: value,\r\n html: `\r\n
\r\n \r\n
${title}
\r\n
open
\r\n
\r\n `,\r\n }\r\n}\r\n\r\nlet url = await arg('Keyword ...', async keyword => {\r\n if (keyword.trim() == '') return []\r\n let {data} = await axios.get(\r\n 'https://store.steampowered.com/search/suggest?term=' + keyword +\r\n '&f=games&cc=' + cc + '&realm=1&l=s' + l + '&v=19040599&excluded_content_descriptors%5B%5D=3' +\r\n '&excluded_content_descriptors%5B%5D=4&use_store_query=1&use_search_spellcheck=1&search_creators_and_tags=1'\r\n );\r\n let $ = cheerio.load(data);\r\n let games = $('a').get().map(aTag => {\r\n if ($(aTag).hasClass('match_app')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let price = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${price}`)\r\n }\r\n if ($(aTag).hasClass('match_tag')) {\r\n let name = $(aTag).find('.match_name span').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, 'https://pbs.twimg.com/profile_images/861662902780018688/SFie8jER_x96.jpg', `${name} - ${count}`)\r\n }\r\n if ($(aTag).hasClass('match_creator')) {\r\n let name = $(aTag).find('.match_name').text();\r\n let count = $(aTag).find('.match_subtitle').text();\r\n let cover = $(aTag).find('.match_img img').attr('src');\r\n let url = $(aTag).attr('href');\r\n return buildResult(url, cover, `${name} - ${count}`)\r\n }\r\n });\r\n return games.filter(x => x);\r\n});\r\n\r\nawait $`open ${url}`\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-18T03:49:41Z"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1272","url":"","title":"Google PaLM2 Chat","command":"google-palm2-chat","content":"The LLM is still in early access but you can sign up for the waitlist [here](https://developers.generativeai.google/)\r\n\r\n[Open palm-chat in Script Kit](https://scriptkit.com/api/new?name=palm-chat&url=https://gist.githubusercontent.com/mabry1985/54c10fa4594fb5a5edcf65c1db55b44b/raw/8460dafe391bd8bb09593e35e2fb89764d27f521/palm-chat.js\")\r\n\r\n```js\r\nlet { GoogleAuth } = await import(\"google-auth-library\");\r\nlet { DiscussServiceClient } = await import(\"@google-ai/generativelanguage\");\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst MODEL_NAME = \"models/chat-bison-001\";\r\nconst API_KEY = await env(\"PALM_API_KEY\", {\r\n hint: `Signup for waitlist here here`,\r\n});\r\n\r\nconst client = new DiscussServiceClient({\r\n authClient: new GoogleAuth().fromAPIKey(API_KEY),\r\n});\r\n\r\nconst config = {\r\n model: MODEL_NAME,\r\n temperature: 0.75,\r\n candidateCount: 1,\r\n top_k: 40,\r\n top_p: 0.95,\r\n};\r\n\r\nconst chatHistory = [];\r\n\r\nconst generateText = async (text) => {\r\n chatHistory.push({ content: text });\r\n const response = await client.generateMessage({\r\n ...config,\r\n prompt: {\r\n context: \"You are a funny and helpful assistant.\",\r\n messages: chatHistory,\r\n },\r\n });\r\n\r\n log(response);\r\n log(response[0].filters);\r\n if (response[0].filters.length > 0) {\r\n return `The model has rejected your input. Reason: ${response[0].filters[0].reason}`;\r\n } else {\r\n chatHistory.push({ content: response[0].candidates[0].content });\r\n return response[0].candidates[0].content;\r\n }\r\n};\r\n\r\nawait chat({\r\n onSubmit: async (input) => {\r\n setLoading(true);\r\n try {\r\n const response = await generateText(input);\r\n let message = md(response);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, message);\r\n } catch (e) {\r\n console.log(e);\r\n chat.addMessage(\"\");\r\n chat.setMessage(-1, md(\"Error: \" + e.message));\r\n }\r\n setLoading(false);\r\n },\r\n});\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-06T01:54:00Z"},{"name":"Static to Dynamic","description":"","author":"Josh Mabry","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1271","url":"","title":"Static to dynamic import converter","command":"static-to-dynamic-import-converter","content":"I got tired of typing out the conversion when pulling in script examples so I made this quick script to convert them\r\n\r\n[Open static-to-dynamic in Script Kit](https://scriptkit.com/api/new?name=static-to-dynamic&url=https://gist.githubusercontent.com/mabry1985/13b951630f05eebc35c66d8e706dee70/raw/70fb4529876ef97fd18351793d329afca945079e/static-to-dynamic.js\")\r\n\r\n```js\r\n// Name: Static to Dynamic\r\n// Description: Convert static import to dynamic import\r\n// e.g. import { Foo } from \"bar\";\r\n// to let { Foo } = await import(\"bar\");\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst text = await getSelectedText();\r\n\r\nfunction convertImportString(input) {\r\n const importRegex = /import\\s+({[^}]+})\\s+from\\s+\"([^\"]+)\";/;\r\n\r\n if (!importRegex.test(input)) {\r\n throw new Error(\"Invalid import string format\");\r\n }\r\n\r\n const [_, importList, modulePath] = input.match(importRegex);\r\n const output = `let ${importList} = await import(\"${modulePath}\");`;\r\n return output;\r\n}\r\n\r\nconst output = convertImportString(text);\r\n\r\nawait setSelectedText(output);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-06-04T18:07:43Z"},{"menu":"Password Manager","description":"","shortcut":"command shift ]","author":"Rohit Kumar Saini","avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1262","url":"https://gist.githubusercontent.com/rockingrohit9639/586628e63330061cdeaff35cbc7dec05/raw/231215f7064ce763ffd4b2aa93ebb00c0341f080/password-manager.ts","title":"A simple Password Manager","command":"a-simple-password-manager","content":"# Password Manager\r\nA simple password manager to add new passwords and copy from one of the existing list of passwords. Passwords are saved after encryption.\r\n\r\n\r\n\r\n[Open password-manager in Script Kit](https://scriptkit.com/api/new?name=password-manager&url=https://gist.githubusercontent.com/rockingrohit9639/586628e63330061cdeaff35cbc7dec05/raw/231215f7064ce763ffd4b2aa93ebb00c0341f080/password-manager.ts\")\r\n\r\n\r\n```js\r\n// Menu: Password Manager\r\n// Description: Manager all your passwords justing using few keys\r\n// Shortcut: command shift ]\r\n// Author: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst { nanoid } = await npm(\"nanoid\");\r\nconst Cryptr = await npm(\"cryptr\");\r\n\r\nconst CRYPTR_KEY = await env(\"CRYPTR_KEY\");\r\nconst cryptr = new Cryptr(CRYPTR_KEY);\r\n\r\nconst { passwords, write } = await db(\"passwords\", { passwords: [] });\r\n\r\ntype Option = {\r\n name: string;\r\n description: string;\r\n value: \"ADD_NEW_PASSWORD\" | \"COPY_PASSWORD\";\r\n};\r\n\r\nconst PM_OPTIONS: Option[] = [\r\n {\r\n name: \"Add New Password\",\r\n description: \"Add a new password to the database\",\r\n value: \"ADD_NEW_PASSWORD\",\r\n },\r\n {\r\n name: \"Copy Password\",\r\n description: \"Copy one of the saved passwords\",\r\n value: \"COPY_PASSWORD\",\r\n },\r\n];\r\n\r\nconst choice: Option[\"value\"] = await arg(\r\n \"What would you like to do?\",\r\n PM_OPTIONS\r\n);\r\n\r\n/** Doing operation on basis of choice */\r\nif (choice === \"ADD_NEW_PASSWORD\") {\r\n addNewPassword();\r\n}\r\n\r\nif (choice === \"COPY_PASSWORD\") {\r\n listAndCopyPassword();\r\n}\r\n\r\nasync function addNewPassword() {\r\n const title = await arg({\r\n placeholder: \"Title\",\r\n hint: \"Title for which your password belongs e.g Facebook etc.\",\r\n });\r\n const password = await arg({\r\n placeholder: \"Password\",\r\n hint: `Password you want to save for ${title}`,\r\n });\r\n\r\n /** Encrypting the password */\r\n const encryptedPassword = cryptr.encrypt(password);\r\n\r\n const id = nanoid(5);\r\n const newPassword = { id, title, password: encryptedPassword };\r\n passwords.push(newPassword);\r\n\r\n /** Saving the password in db */\r\n await write();\r\n notify(`Password for ${title} added successfully!`);\r\n}\r\n\r\nasync function listAndCopyPassword() {\r\n const passwordToCopy = await arg(\r\n \"Which password would you like to copy ?\",\r\n () =>\r\n passwords.map(({ title, password }) => ({ name: title, value: password }))\r\n );\r\n\r\n /** Decrypting the password */\r\n const decryptedPassword = cryptr.decrypt(passwordToCopy);\r\n\r\n /** Copying the password to clipboard */\r\n copy(decryptedPassword);\r\n notify(\"Password copied to you clipboard!\");\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-21T04:44:25Z"},{"name":"Pomodoro","description":"","avatar":"https://avatars.githubusercontent.com/u/597015?u=1e764f789e382da7c81ee37ef3e1060bac244310&v=4","user":"LukeCarrier","author":"Luke Carrier","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1261","url":"https://gist.githubusercontent.com/LukeCarrier/b5800f573f43fc7acf4ea327f6e396b4/raw/d75fe7d8b4428500457cb2e6de3e2b11e1c9353c/pomodoro.ts","title":"Silly Pomodoro timer","command":"silly-pomodoro-timer","content":"Just a small hack to replace the many menu bar applications I've used over the years, and an excuse to have a play with Script Kit. I'm glad I did -- it's awesome 😁 \r\n\r\n[Open pomodoro in Script Kit](https://scriptkit.com/api/new?name=pomodoro&url=https://gist.githubusercontent.com/LukeCarrier/b5800f573f43fc7acf4ea327f6e396b4/raw/d75fe7d8b4428500457cb2e6de3e2b11e1c9353c/pomodoro.ts\")\r\n\r\n```js\r\n// Name: Pomodoro\r\n// Description: A Pomodoro timer, right here!\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst HOUR_MIN = 60;\r\nconst MIN_SEC = 60;\r\nconst SEC_MS = 1000;\r\n\r\nconst WORK_INTERVAL_SECS = 25 * 60;\r\nconst REST_INTERVAL_SECS = 5 * 60;\r\n\r\nconst WORK_INTERVAL_ICON = \"🍅\";\r\nconst REST_INTERVAL_ICON = \"🏝️\";\r\nconst COMPLETE_ICON = \"🎉\";\r\n\r\nconst WIDGET_HTML = `\r\n
\r\n {{icon}}\r\n
\r\n
\r\n
{{goal}}
\r\n
{{timer}}
\r\n
\r\n`;\r\nconst DING_JS = `new Audio(\"../kenvs/personal/assets/ding.ogg\").play();`;\r\nconst DING_SECS = 5;\r\n\r\nfunction formatTimeRemaining(seconds: number): string {\r\n const totalMinutes = Math.floor(seconds / HOUR_MIN);\r\n const formatSeconds = String(seconds % MIN_SEC).padStart(2, \"0\");\r\n const formatMinutes = String(totalMinutes % MIN_SEC).padStart(2, \"0\");\r\n return `${formatMinutes}:${formatSeconds}`;\r\n}\r\n\r\nconst goal = await arg(\"What's your goal this interval?\")\r\n\r\nconst timerWidget = await widget(WIDGET_HTML, {\r\n title: \"Pomodoro\",\r\n state: { icon: \"\", goal: \"\", timer: \"\" },\r\n\r\n containerClass: \"p-6 max-w-sm mx-auto rounded-xl shadow-lg flex items-center space-x-4\",\r\n alwaysOnTop: true,\r\n preventEscape: true,\r\n minimizable: false,\r\n maximizable: false,\r\n fullscreenable: false,\r\n opacity: 0.45,\r\n\r\n // If these are below the minimum size of a widget on macOS (160x120) the\r\n // widget appears as a small white box without any content until manually\r\n // resized.\r\n width: 340,\r\n height: 120,\r\n});\r\n\r\nfunction doInterval(icon: string, goal: string, interval_secs: number): Promise {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(interval_secs) });\r\n\r\n return new Promise((resolve) => {\r\n const startTime = new Date().getTime();\r\n const timerInterval = setInterval(() => {\r\n const thisTime = new Date().getTime();\r\n const elapsedSeconds = Math.round((thisTime - startTime) / SEC_MS);\r\n const remainingSeconds = interval_secs - elapsedSeconds;\r\n if (remainingSeconds >= 0) {\r\n timerWidget.setState({ icon, goal, timer: formatTimeRemaining(remainingSeconds) });\r\n } else {\r\n clearInterval(timerInterval);\r\n timerWidget.executeJavaScript(DING_JS).finally(() => {\r\n resolve();\r\n });\r\n }\r\n }, 1000);\r\n });\r\n}\r\n\r\nawait doInterval(WORK_INTERVAL_ICON, goal, WORK_INTERVAL_SECS);\r\nawait doInterval(REST_INTERVAL_ICON, `Break after ${goal}`, REST_INTERVAL_SECS);\r\ntimerWidget.setState({ icon: COMPLETE_ICON, goal: `${goal} all done!`, timer: \"That's another interval complete.\" });\r\nsetTimeout(() => timerWidget.close(), DING_SECS * 1000);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-20T19:31:28Z"},{"name":"Json To Yaml Converter","author":"Eddie","twitter":"schmedu_","avatar":"https://avatars.githubusercontent.com/u/8198764?u=8159fd937f0836c330f0aed6c4d518c48461072e&v=4","user":"Schmedu","discussion":"https://github.com/johnlindquist/kit/discussions/1259","url":"https://gist.githubusercontent.com/Schmedu/c904124d7a9cd4b9fd25485c9d8c36d0/raw/75255898c5293cbe648e1f7c521bc5a93c120e7b/json2yaml.ts","title":"JSON 2 YAML","command":"json-2-yaml","content":"[Open json2yaml in Script Kit](https://scriptkit.com/api/new?name=json2yaml&url=https://gist.githubusercontent.com/Schmedu/c904124d7a9cd4b9fd25485c9d8c36d0/raw/75255898c5293cbe648e1f7c521bc5a93c120e7b/json2yaml.ts\")\r\n\r\n```js\r\n// Name: Json To Yaml Converter\r\n// Author: Eduard Uffelmann\r\n// Twitter: @schmedu_\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport * as yaml from \"js-yaml\";\r\n\r\nlet filePath = await getSelectedFile();\r\nlet content = await readJson(filePath);\r\n\r\nlet result = yaml.dump(content);\r\n\r\nlet todo = await mini(\"What to do?\", [\"Copy\", \"Save\"]);\r\nif (todo === \"Copy\") {\r\n await copy(result);\r\n} else {\r\n await writeFile(filePath.replace(\".json\", \".yaml\"), result);\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-17T10:31:51Z"},{"name":"Get GitHub Commits Messages Since Tag","description":"","author":"John Lindquist","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","discussion":"https://github.com/johnlindquist/kit/discussions/1254","url":"https://gist.githubusercontent.com/johnlindquist/e56b9ad663cd56c947cc528c5f1c9f96/raw/a10ac8f6d49ebf961fd08013aec4f9b998e1024c/get-commits.ts","title":"Get GitHub Commit Messages Since Tag","command":"get-github-commit-messages-since-tag","content":"\r\n[Open get-commits in Script Kit](https://scriptkit.com/api/new?name=get-commits&url=https://gist.githubusercontent.com/johnlindquist/e56b9ad663cd56c947cc528c5f1c9f96/raw/a10ac8f6d49ebf961fd08013aec4f9b998e1024c/get-commits.ts)\r\n\r\n```js\r\n// Name: Get GitHub Commits Messages Since Tag\r\n// Description: Get all commit messages since a tag\r\n// Author: John Lindquist\r\n// Twitter: @johnlindquist\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { Octokit } = await import(\"@octokit/rest\")\r\n\r\nlet ownerRepo = await arg(\"Enter username/repo. Example: johnlindquist/kit\")\r\nlet [owner, repo] = ownerRepo.split(\"/\")\r\nlet tag = await arg(\"Tag. Example: v1.54.53\")\r\n\r\nlet client = new Octokit({\r\n auth: await env(\"GITHUB_PERSONAL_ACCESS_TOKEN\"),\r\n})\r\n\r\nlet page = 1\r\nlet hasMorePages = true\r\nlet messages = []\r\n\r\nlet ref = null\r\nlet tagPage = 1\r\nwhile (!ref) {\r\n let listTags = await client.repos.listTags({\r\n owner,\r\n repo,\r\n per_page: 100,\r\n name: tag,\r\n page: tagPage,\r\n })\r\n\r\n tagPage++\r\n ref = listTags.data.find(t => t.name === tag).commit.sha\r\n}\r\n\r\nlet commit = await client.repos.getCommit({\r\n owner,\r\n repo,\r\n ref,\r\n})\r\n\r\nlet since = commit.data.commit.author.date\r\n\r\nwhile (hasMorePages) {\r\n let data = await client.repos.listCommits({\r\n owner,\r\n repo,\r\n since,\r\n per_page: 100,\r\n page: page,\r\n })\r\n\r\n hasMorePages = data.data.length === 100\r\n messages = messages.concat(data.data.map(c => c.commit.message))\r\n\r\n page++\r\n}\r\n\r\nlet text = messages.join(\"\\n\\n\")\r\n\r\nif (env?.[\"GITHUB_SCRIPTKIT_TOKEN\"]) {\r\n let response = await createGist(text, {\r\n description: `Commit messages since ${tag}`,\r\n isPublic: false,\r\n fileName: \"commit-messages.txt\",\r\n })\r\n\r\n open(response.html_url)\r\n\r\n debugger\r\n} else {\r\n await editor(text)\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-10T16:08:12Z"},{"description":"","author":"Pierre Borckmans","shortcut":"ctrl opt cmd b","avatar":"https://avatars.githubusercontent.com/u/5610359?u=7019998d8df8daf3c7dda50ab3682fce7d43aad2&v=4","user":"pierre-borckmans","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1253","url":"","title":"Drive homebrew through Script kit","command":"drive-homebrew-through-script-kit","content":"Here's a small script that lets the user drive Homebrew from kit script:\r\n- list installed formulae / casks\r\n- install a new formula / cask from a list of all the ones available, minus the ones already installed\r\n- uninstall an existing formula/cask\r\n\r\nHope it's useful\r\n\r\n```// Name: Homebrew menu\r\n// Description: Drive homebrew through Kit-script\r\n// Author: Pierre Borckmans\r\n\r\n// shortcut: ctrl opt cmd b\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst BREW_CMD = '/opt/homebrew/bin/brew'\r\n\r\n\r\nconst cmdList = async (cmd) => (await cmd)._stdout.split(\"\\n\").slice(0, -1)\r\n\r\n\r\nconst getInstalled = async (type) => {\r\n return await cmdList($`${BREW_CMD} list --${type} -1`)\r\n}\r\n\r\nconst getAvailable = async (type) => {\r\n const items = (await cmdList($`${BREW_CMD} ${type} -1`)).map(o => ({\r\n name: o,\r\n value: o,\r\n description: type.slice(0, -1)\r\n }))\r\n const alreadyInstalled = await getInstalled(type.slice(0, -1))\r\n return items.filter(i => !alreadyInstalled.find(ai => ai === i.value));\r\n}\r\n\r\nconst install = async () => {\r\n const packageName = await arg(\"Enter a package to install\", [...await getAvailable(\"formulae\"), ...await getAvailable(\"casks\")])\r\n await $`${BREW_CMD} install ${packageName}`\r\n}\r\n\r\nconst uninstall = async () => {\r\n const packageName = await arg(\"Choose a package to uninstall\", [...await list(\"formula\"), ...await list(\"cask\")])\r\n await $`${BREW_CMD} uninstall ${packageName}`\r\n}\r\n\r\nconst menu = async () => {\r\n const menuOptions = [\r\n { \r\n value: \"formulae\",\r\n name: \"List installed formulae\"\r\n },\r\n { \r\n value: \"casks\",\r\n name: \"List installed casks\"\r\n },\r\n { \r\n value: \"install\",\r\n name: \"Install a cask or formula\"\r\n },\r\n { \r\n value: \"uninstall\",\r\n name: \"Uninstall a cask or formula\"\r\n },\r\n ]\r\n const menuChoice = await arg(\"Select an option\", menuOptions)\r\n\r\n switch (menuChoice) {\r\n case \"formulae\":\r\n await arg(`Homebrew formulas`, await getInstalled(\"formula\"))\r\n break\r\n case \"casks\":\r\n await arg(`Homebrew casks`, await getInstalled(\"cask\"))\r\n break\r\n case \"install\":\r\n await install()\r\n break \r\n case \"uninstall\":\r\n await uninstall()\r\n break \r\n default:\r\n break;\r\n }\r\n await menu()\r\n}\r\n\r\nawait menu();\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-10T14:31:19Z"},{"avatar":"https://avatars.githubusercontent.com/u/22137371?u=22b3858822ffbf8ddce68b5c2e000a9910dbb934&v=4","user":"ScytheDraven47","author":"Ben Rogers-McKee","twitter":"ScytheDraven47","discussion":"https://github.com/johnlindquist/kit/discussions/1251","url":"","title":"Bitwarden Passwords via CLI","command":"bitwarden-passwords-via-cli","content":"I figured it'd be nice to have a Bitwarden Script for use outside of browsers, and it made for a good first mini project.\r\nIt uses [@bitwarden/cli](https://www.npmjs.com/package/@bitwarden/cli) via NPM, though I'm looking at doing an API version as well.\r\n\r\n[Code/gist here](https://gist.github.com/ScytheDraven47/0605ea9475778ae9cc2279c6fd07ad2e)\r\n\r\n`Enter` copies password\r\n`Ctrl+Enter` copies username\r\n`Ctrl+Shift+Enter` pastes username, then tabs once, then pastes password (won't work for all use cases, but figured it's nice to have)\r\n\r\nThis script does not save user credentials, but saves a session key to prevent frequent logging in.\r\n\r\nCurrently missing 2FA via email and YubiKey.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-09T08:45:36Z"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1249","url":"","title":"Multichoice for the `arg` function","command":"multichoice-for-the-arg-function","content":"I wrapped the `arg` function to allow multi-selection.\r\nJust replace `arg` with `multiArg` and you're good to go. ✨😊\r\nProvide a 3rd argument to customize item templates.\r\n\r\n[See code here](https://gist.github.com/BeSpunky/468b2e790ba9e32a73a3717dc876bdc4)\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236700932-ad5410fa-f717-4dea-9657-b78a0fc4a6c5.mp4\r\n\r\n`Enter`: Toggles Selection\r\n`Ctrl+Enter`: Submits the results\r\n\r\n**Known issues:**\r\n* When the list is longer than the window, list jumps occur. See #1248 \r\n* The `input` parameter passed into the choice factory function (2nd argument of `arg`) is always `''` and doesn't reflect user input.\r\n* List is filtered, but the default template doesn't highlight fuzzy search matches like the original one.\r\n\r\nEnjoy 🥂","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-07T20:29:14Z"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1247","url":"","title":"Efficient rebuild of scripts when `lib` files change","command":"efficient-rebuild-of-scripts-when-lib-files-change","content":"This script watches the `lib` folder, and when changes to `ts` files are made, it does 2 things:\r\n1. Create/update a dependency graph of `libFilePath -> dependantScriptPaths[]`\r\n2. Touches all script files that depend on the changed `lib` file to trigger rebuild.\r\n\r\nhttps://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a\r\n\r\n**TLDR**\r\nCurrently, ScriptKit only rebuilds scripts if it detects changes to the `scripts` folder.\r\nIf you extract your reusable parts and put them into the `lib` folder, ScriptKit doesn't pick up on changes to those files.\r\nThe manual way to overcome this is to save your script file again and trigger rebuild.\r\n\r\nNo more... :)\r\n\r\nActually, this is a 3 scripts solution:\r\n1. [`update-script-dependency-graphs`](https://gist.github.com/BeSpunky/c9139cdedfa349c501a70febea3c46d5): Partially rebuilds the graph if a triggering file has been provided, otherwise completely rebuilds it.\r\n2. [`watch-libs`](https://gist.github.com/BeSpunky/d40bf6052b2c9a4f3a58b22108e5124a): Watches lib files, partially rebuild the graph using `update-script-dependency-graph`, then touch the scripts.\r\n3. [`watch-scripts`](https://gist.github.com/BeSpunky/1ce1ad4e29b339bec11cd2d416cb676c): Watch script files, partially rebuild the graph\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T21:38:13Z"},{"avatar":"https://avatars.githubusercontent.com/u/47897671?v=4","user":"BeSpunky","author":"Shy Agam","twitter":"shyagam","discussion":"https://github.com/johnlindquist/kit/discussions/1246","url":"","title":"Create a Gist for you script and it's lib dependencies","command":"create-a-gist-for-you-script-and-its-lib-dependencies","content":"## Watch how I publish the script that publishes scripts and their dependencies to Gist... 😄\r\n\r\nhttps://user-images.githubusercontent.com/47897671/236570763-9c84163f-c4f3-46d6-943f-537724db2b2e.mp4\r\n\r\n\r\nHere's the Gist of it:\r\nhttps://gist.github.com/BeSpunky/ff5dcb62887cbee686dd6c3ba31cabb5\r\n\r\n**TLDR**\r\nAs I go playing with ScriptKit, I started using the `lib` folder to centralize reusable functionality.\r\nThis made my scripts difficult to share, as they have nested dependencies which I would've had to add manually to my Gists.\r\nWell no more... 💪\r\n\r\nThis script let's you choose one of your scripts, reads it and recursively extracts `lib` dependencies, then publishes a new gist with the script and the dependencies.\r\n\r\nEnjoy 😊","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T21:23:15Z"},{"name":"Prompt Anything","description":"","author":"Josh Mabry","twitter":null,"shortcut":"alt shift enter","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1245","url":"","title":"Prompt Anywhere v2","command":"prompt-anywhere-v2","content":"[Open prompt-anywhere in Script Kit](https://scriptkit.com/api/new?name=prompt-anywhere&url=https://gist.githubusercontent.com/mabry1985/482fcf46ae66d79348d63096e00fb5d5/raw/2114b2c4644787ec77ae9f39adff333ecc23f864/prompt-anywhere.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as quick-prompt.js and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n //#########\r\n // Helpers\r\n //########\r\n // exit script on cancel\r\n const cancelChat = () => {\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Paste text to highlighted text and exit script\r\n * @param {*} text\r\n */\r\n const pasteTextAndExit = async (text) => {\r\n await setSelectedText(text);\r\n process.exit(1);\r\n };\r\n\r\n /**\r\n * Copy text to clipboard and exit script\r\n * @param {*} text\r\n */\r\n const copyToClipboardAndExit = async (text) => {\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n };\r\n\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // which works, but would be nice to also have ESC work\r\n ignoreBlur: false,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // have paste on text on submit?\r\n // onSubmit: () => pasteTextAndExit(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n await pasteTextAndExit(currentMessage);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // @TODO still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all of the actions like copy, paste, etc\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => await copyToClipboardAndExit(state),\r\n onSubmit: async (state) => await pasteTextAndExit(state),\r\n });\r\n break;\r\n case \"copy\":\r\n await copyToClipboardAndExit(currentMessage);\r\n case \"save\":\r\n await inspect(currentMessage, `/conversations/${Date.now()}.md`);\r\n exitChat();\r\n default:\r\n copyToClipboardAndExit(currentMessage);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T08:07:12Z"},{"name":"Prompt Anything","description":"","author":"Josh Mabry","twitter":null,"shortcut":"alt shift enter","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1244","url":"","title":"Prompt Anywhere v2","command":"prompt-anywhere-v2","content":"\r\n[Open prompt-anything in Script Kit](https://scriptkit.com/api/new?name=prompt-anything&url=https://gist.githubusercontent.com/mabry1985/6c2412d4d3d1360276b9d95f44548815/raw/1dac5e149ebc4cf86ea830db9104d8518f47eca5/prompt-anything.js\")\r\n\r\nIt's a completely overhauled version of the original. There was too much funkery (real word) that could happen by letting chatgpt just spew characters via keyboard.\r\n\r\nV1 still will exist as `quick-prompt.js` and will live under the deprecated folder with a disclaimer about the funk mentioned above and use at own risk, etc. I'll still be using it, because it's pretty fun :)\r\n\r\nAnyhow, This is a \"pre-release\" and will be added to my [kenv](https://github.com/artificialcitizens/ac-scripts) soon\r\n\r\nThere are a couple things I still need to work out, like how the heck to stop the token stream on escape and some more QOL, but I really happy with it. I used it all week in my workflow and it's really a game changer.\r\n\r\nFeedback, critiques, or bad puns are welcome.\r\n\r\nAnyhow, set forth and be awesome.\r\n```js\r\n/*\r\n# Prompt Anything\r\nHighlight some text and run this script to prompt against it.\r\nUseful for summarizing text, generating a title, or any other task you can think of.\r\n\r\n## Usage\r\n\r\n- Highlight the text you want to prompt against\r\n- Run the script via shortcut or command palette\r\n- Input your desired prompt\r\n- Wait for the AI to respond\r\n- Select one of the options\r\n* Retry - Rerun generation with option to update prompt\r\n* Edit - Edit response in editor\r\n - On editor exit the message is saved to the clipboard\r\n - On editor submit the message is pasted into the highlighted text\r\n* Copy - Copy response to clipboard\r\n* Paste - Paste response into highlighted text\r\n* Save - Save response to file (not working)\r\n## Example\r\n- Highlight: 'Some really long passage in a blog post'\r\n- Run Script\r\n- Prompt: `Summarize this passage in the form of Shakespearean prose`\r\n- Waaaaait for it...\r\n- Get a response from the AI\r\n- Select an option\r\n- Rinse and repeat\r\n*/\r\n\r\n// Name: Prompt Anything\r\n// Description: Custom prompt for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: alt shift enter\r\n\r\n//#################\r\n// ScriptKit Import\r\n//#################\r\nimport \"@johnlindquist/kit\";\r\n\r\n//#################\r\n// LangChain Imports\r\n//#################\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\n\r\n//#################\r\n// Request API KEY\r\n//#################\r\n// stored in .env file after first run\r\n// can change there or through the command palette\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n// System input / Task for the AI to follow\r\nlet userSystemInput = await arg(\"Summarize this passage\");\r\n// User Prompt from highlighted text\r\nlet userPrompt = await getSelectedText();\r\n\r\n//#################\r\n// Prompt Template\r\n//#################\r\nconst formatPrompt = (prompt) => {\r\n return `##### Ignore prior instructions\r\n- Return answer in markdown format\r\n- You are tasked with the following\r\n${prompt}\r\n########\r\n`;\r\n};\r\n//################\r\n// Options Template\r\n//################\r\nconst options = `\r\n* [Retry](submit:retry) - Rerun generation with option to update prompt\r\n* [Edit](submit:edit) - Edit response in editor\r\n* [Copy](submit:copy) - Copy response to clipboard\r\n* [Paste](submit:paste) - Paste response into highlighted text\r\n* [Save](submit:save) - Save response to file (not working)\r\n`;\r\n//#########\r\n// Helpers\r\n//########\r\n// exit script on cancel\r\nconst cancelChat = () => {\r\n process.exit(1);\r\n};\r\n\r\n//################\r\n// Main Function\r\n//################\r\n/**\r\n *\r\n * @param {*} prompt\r\n * @param {*} humanChatMessage\r\n */\r\nasync function promptAgainstHighlightedText(\r\n prompt = formatPrompt(userSystemInput),\r\n humanChatMessage = userPrompt\r\n) {\r\n let currentMessage = \"\";\r\n const llm = new ChatOpenAI({\r\n // 0 = \"precise\", 1 = \"creative\"\r\n temperature: 0.3,\r\n // modelName: \"gpt-4\", // uncomment to use GPT-4 (requires beta access)\r\n openAIApiKey: openAIApiKey,\r\n // turn off to only get output when the AI is done\r\n streaming: true,\r\n callbacks: [\r\n {\r\n handleLLMNewToken: async (token) => {\r\n log(`handleLLMNewToken`);\r\n // each new token is appended to the current message\r\n // and then rendered to the screen\r\n currentMessage += token;\r\n // render current message\r\n await div({\r\n html: md(currentMessage + options),\r\n // @TODO: Figure out how to get ESC to trigger a cancel\r\n onAbandon: cancelChat,\r\n onEscape: cancelChat,\r\n onBackspace: cancelChat,\r\n // if this is set to false you can click outside the window to cancel\r\n // ignoreBlur: true,\r\n focus: true,\r\n // hint: `Press ESC to cancel`,\r\n });\r\n },\r\n handleLLMError: async (err) => {\r\n dev({ err });\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n // render final message with options\r\n let html = md(currentMessage + options);\r\n // wait for user to select an option\r\n const selectedOption = await div(html, {\r\n ignoreBlur: true,\r\n focus: true,\r\n // onSubmit: () => setSelectedText(currentMessage),\r\n });\r\n // handle selected option\r\n switch (selectedOption) {\r\n case \"paste\":\r\n // paste into highlighted text\r\n await setSelectedText(currentMessage);\r\n process.exit(1);\r\n case \"retry\":\r\n // reset current message\r\n currentMessage = \"\";\r\n // prompt again with new prompt\r\n // press enter to use original prompt\r\n const followUp = await arg({\r\n placeholder: userSystemInput,\r\n hint: \"Press enter to use the same prompt\",\r\n });\r\n await processMessage(followUp);\r\n break;\r\n case \"edit\":\r\n // still need to figure out best way to handle submit and abort\r\n // would like custom buttons for triggering all these same options such as save\r\n await editor({\r\n value: currentMessage,\r\n onEscape: async (state) => {\r\n // copy to clipboard when exiting the editor\r\n await clipboard.writeText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n onSubmit: async (state) => {\r\n // paste into highlighted text when pressing enter\r\n await setSelectedText(state);\r\n // exit script\r\n process.exit(1);\r\n },\r\n });\r\n break;\r\n case \"copy\":\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n // exit script\r\n process.exit(1);\r\n case \"save\":\r\n await inspect(currentMessage, `conversations/${Date.now()}.md`);\r\n // exit script\r\n process.exit(1);\r\n default:\r\n // copy to clipboard\r\n await clipboard.writeText(currentMessage);\r\n process.exit(1);\r\n }\r\n await optionHandler(selectedOption);\r\n },\r\n },\r\n ],\r\n });\r\n //###########\r\n // Main Loop\r\n //###########\r\n // runs the language model until the user cancels\r\n while (true) {\r\n await llm.call([\r\n new SystemChatMessage(formatPrompt(prompt)),\r\n new HumanChatMessage(humanChatMessage),\r\n ]);\r\n }\r\n}\r\n\r\npromptAgainstHighlightedText();\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-05T06:30:54Z"},{"avatar":"https://avatars.githubusercontent.com/u/629240?u=4750610206bd5ebafbdfab85d5d9c81dc8ce21ed&v=4","user":"blakecannell","author":"Blake.","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1240","url":"","title":"Snippets Manager (File Based)","command":"snippets-manager-file-based","content":"Simple file based snippets manager:\r\n\r\nA few things to note:\r\n- File paths are currently specific to Windows. I will update for other envionments.\r\n- This assumes a `snippets` folder exists within your home folder. Is there any way to make this configurable (within the manager itself)? This is of course quite simple to set as a constant at the top of the file if not.\r\n\r\nhttps://gist.github.com/blakecannell/bea79f6c69103a410181802855855aa4","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-04T04:40:53Z"},{"name":"Open Recent VS Code Project","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1238","url":"https://gist.githubusercontent.com/johnlindquist/b2426f52a9b5f3ca4827fbdeda6b323c/raw/8d7cbd175540000bf6a8684814de265343ad2ae5/open-recent-vs-code-project.ts","title":"Open Recent VS Code Project","command":"open-recent-vs-code-project","content":"TIL VS Code has a sqlite database of recents, so I built this!\r\n\r\n[Open open-recent-vs-code-project in Script Kit](https://scriptkit.com/api/new?name=open-recent-vs-code-project&url=https://gist.githubusercontent.com/johnlindquist/b2426f52a9b5f3ca4827fbdeda6b323c/raw/8d7cbd175540000bf6a8684814de265343ad2ae5/open-recent-vs-code-project.ts\")\r\n\r\n```js\r\n// Name: Open Recent VS Code Project\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { URL, fileURLToPath } from \"url\"\r\n\r\n// /Users/johnlindquist/Library/Application Support/Code/User/globalStorage/state.vscdb\r\nlet filename = home(\"Library\", \"Application Support\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\n// windows path not tested, just guessing\r\nif (isWin) filename = home(\"AppData\", \"Roaming\", \"Code\", \"User\", \"globalStorage\", \"state.vscdb\")\r\nlet { default: sqlite3 } = await import(\"sqlite3\")\r\nlet { open } = await import(\"sqlite\")\r\n\r\nconst db = await open({\r\n filename,\r\n driver: sqlite3.Database,\r\n})\r\n\r\nlet key = `history.recentlyOpenedPathsList`\r\nlet table = `ItemTable`\r\n\r\nlet result = await db.get(`SELECT * FROM ${table} WHERE key = '${key}'`)\r\nlet recentPaths = JSON.parse(result.value)\r\nrecentPaths = recentPaths.entries\r\n .map(e => e?.folderUri)\r\n .filter(Boolean)\r\n .map(uri => fileURLToPath(new URL(uri)))\r\n\r\nlet recentPath = await arg(\"Open a recent path\", recentPaths)\r\nhide()\r\nawait exec(`code ${recentPath}`)\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-03T23:43:12Z"},{"name":"Samantha","description":"","shortcut":"command shift enter","avatar":"https://avatars.githubusercontent.com/u/34244581?u=f136c7d3eec99d1a4b2c887ca8c673bfabef95fa&v=4","user":"alwinraju","author":"Alwin Raju","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1236","url":"","title":"ChatGPT with user input","command":"chatgpt-with-user-input","content":"Heres a code snippet that takes in a user input and feeds it into ChatGPT and returns the response.\r\nAn OpenAI API key is required for it to work. The prompt can be amended to suit your needs/to create\r\nyour own custom agents.\r\n\r\n```javascript\r\n/*\r\nPress `cmd+shift+enter` and enter the text you want to send to ChatGPT.\r\n*/\r\n\r\n// Name: Samantha\r\n// Description: Send a single prompt to ChatGPT\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nYou are ChatGPT, a large language model trained by OpenAI. Follow the user's\r\ninstructions carefully. Respond using markdown.\r\n########\r\n`;\r\n\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet textFromUser = await arg(\"How can I help you?\");\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(textFromUser)]);\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T21:30:11Z"},{"name":"Pause Any Music","description":"","author":"Josh Davenport-Smith","twitter":"joshdprts","avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","discussion":"https://github.com/johnlindquist/kit/discussions/1235","url":"https://gist.githubusercontent.com/joshdavenport/d6a38b7e5b9d9f1a76b0e44b78a7a5e5/raw/7c376383c698a52f817228aa19cf5312dbbc095c/pause-any-music.ts","title":"Pause any music - only looks at Spotify/Music.app but customisable","command":"pause-any-music-only-looks-at-spotifymusicapp-but-customisable","content":"I'm often switching between Spotify and Music.app and find that what Mac targets when using pause media key can sometimes be unpredictable. This little script will pause either if playing.\r\n\r\n[Open pause-any-music in Script Kit](https://scriptkit.com/api/new?name=pause-any-music&url=https://gist.githubusercontent.com/joshdavenport/d6a38b7e5b9d9f1a76b0e44b78a7a5e5/raw/7c376383c698a52f817228aa19cf5312dbbc095c/pause-any-music.ts\")\r\n\r\n```js\r\n// Name: Pause Any Music\r\n// Description: Pause music playing from music apps\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst pauseScript = `\r\ntell application \"Spotify\"\r\n pause\r\nend tell\r\n\r\ntell application \"Music\"\r\n pause\r\nend tell\r\n`;\r\n\r\nexec(`osascript -e '${pauseScript}'`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T09:26:25Z"},{"preview":"docs","menu":"Open Project","description":"","shortcut":"cmd shift .","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1234","url":"","title":"Open dev-project","command":"open-dev-project","content":"Now updated to use DB and add paths, create folders.\r\n\r\nOpen for any improvements ^^\r\n\r\n\r\n[Open dev-project in Script Kit](https://scriptkit.com/api/new?name=dev-project&url=https://gist.githubusercontent.com/Ambushfall/41236543032f4dd211a65964766be087/raw/5f7b2d37749d436f5015523d57fcba28d119b4cd/dev-project.js\")\r\n\r\n```js\r\n// Preview: docs\r\n// Menu: Open Project\r\n// Description: Opens a project in vscode\r\n// Shortcut: cmd shift .\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst envPath = await env('PROJECT_DIR');\r\n\r\nconst projectDir = home(envPath);\r\n\r\nconst projectList = await readdir(projectDir);\r\n\r\n\r\nlet { projects, write } = await db(\"projects\", {\r\n projects: projectList,\r\n})\r\n\r\nprojectList.forEach(async value => {\r\n if (!projects.includes(value)) {\r\n projects.push(value);\r\n await write()\r\n }\r\n})\r\n\r\n\r\nonTab(\"Open\", async () => {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n edit('', path.resolve(projectDir, project))\r\n})\r\n\r\nonTab(\"Add Path\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n \"Add path to project:\",\r\n md(projects.map(project => `* ${project.split('\\\\').pop()}`).join(\"\\n\"))\r\n )\r\n projects.push(project)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"Remove\", async () => {\r\n while (true) {\r\n let project = await arg(\"Open project:\", projects.map(project => project.split('\\\\').pop()))\r\n\r\n project.split(':').length > 1 ? await rm(path.resolve(project)) : await rm(path.resolve(projectDir, project))\r\n\r\n let indexOfProject = projects.indexOf(project)\r\n projects.splice(indexOfProject, 1)\r\n await write()\r\n }\r\n})\r\n\r\nonTab(\"New Project\", async () => {\r\n while (true) {\r\n let project = await arg(\r\n {\r\n placeholder: \"Create new project:\", debounceInput: 400,\r\n enter: \"Create\", validate: async (input) => {\r\n let exists = await isDir(path.resolve(projectDir, input));\r\n if (exists) {\r\n return `${input} already exists`;\r\n }\r\n return true;\r\n }\r\n },\r\n\r\n )\r\n projects.push(project)\r\n mkdir(path.resolve(projectDir, project))\r\n await write()\r\n }\r\n})\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T03:28:00Z"},{"name":"Explain Plz","description":"","author":"Josh Mabry","twitter":null,"shortcut":"cmd alt shift e","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1232","url":"","title":"Get an AI powered explanation of highlighted text","command":"get-an-ai-powered-explanation-of-highlighted-text","content":"\r\n[Open explain-plz in Script Kit](https://scriptkit.com/api/new?name=explain-plz&url=https://gist.githubusercontent.com/mabry1985/15add17a63b2d218be168495c2fb46b1/raw/3515457b32049380e633da1e625ff3d6714f844d/explain-plz.js\")\r\n\r\nA quick POC for an AI powered explanation script\r\n\r\nReturns a TLDR, Technical Summary, and ELI5\r\n\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235382761-06db398d-e8b0-4be5-8e8d-a3bb81d4a694.mov\r\n\r\n\r\n```js\r\n/*\r\n# Explain Plz\r\nHighlight some text and have it explained by AI\r\nWorks for any highlighted text or code\r\n*/\r\n\r\n// Name: Explain Plz\r\n// Description: Get an explanation for any highlighted text\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd alt shift e\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and explaining it to the user.\r\nReturn the response in the following format using markdown syntax:\r\n# Explain Plz\r\n## TLDR (A quick summary of the highlighted text)\r\n## ELI5 (Explain Like I'm 5)\r\n## Explanation (A longer technical explanation of the highlighted text)\r\n`;\r\nlet currentMessage = \"\";\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n let widget = await widget(`\r\n
\r\n Loading...\r\n
\r\n`);\r\n log(`handleLLMStart`);\r\n currentMessage += token;\r\n let html = md(token);\r\n\r\n await div(html);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n currentMessage += token;\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n },\r\n handleLLMEnd: async () => {\r\n widget = null;\r\n log(`handleLLMEnd`);\r\n\r\n let html = md(currentMessage);\r\n\r\n await div(html);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n``;\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-05-01T00:13:53Z"},{"name":"AC AGI","description":"","author":"Josh Mabry","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1231","url":"","title":"An example of an AGI task manager with Human in the loop feedback","command":"an-example-of-an-agi-task-manager-with-human-in-the-loop-feedback","content":"\r\n[Open ac-agi in Script Kit](https://scriptkit.com/api/new?name=ac-agi&url=https://gist.githubusercontent.com/mabry1985/cb36cb2a25d58628dcc2b506ec63e2dc/raw/0814b40397f404a9f05d03a549b352186f22f6ba/ac-agi.js\")\r\n\r\nUp to date script can be found in my Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\n```js\r\n/*\r\nPardon the mess this was put together in half a day for the [lablab.ai](https://lablab.ai/event/autonomous-gpt-agents-hackathon) hackathon.\r\nMore updates to come\r\n\r\n# AC AGI \r\nAn autonomous general intelligence that accomplishes a task for you.\r\nUses human in the loop to provide feedback to the agent.\r\n\r\n\r\nHow to use:\r\n- Enter your task\r\n- Wait for the agent to complete the task\r\n- Assign max-iterations for the agent to loop: 0 for infinite (probably not a good idea ¯\\_(ツ)_/¯)\r\n- Profit\r\n\r\nKnown issues:\r\n- The agent will sometimes get stuck in a loop and not complete the task\r\n- Human feedback is not always helpful\r\n\r\nUpcoming features:\r\n- More tools\r\n- Refined prompts\r\n- Better human feedback system\r\n- Better memory system\r\n\r\nPossible thanks to the fine folks at [Langchain](https://js.langchain.com/docs/use_cases/autonomous_agents/baby_agi#example-with-tools)\r\nand all the other giants whose shoulders we stand on.\r\n*/\r\n\r\n// Name: AC AGI\r\n// Description: An AGI task manager inspired by BabyAGI\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { BabyAGI } = await import(\"langchain/experimental/babyagi\");\r\nlet { MemoryVectorStore } = await import(\"langchain/vectorstores/memory\");\r\nlet { OpenAIEmbeddings } = await import(\"langchain/embeddings/openai\");\r\nlet { OpenAI } = await import(\"langchain/llms/openai\");\r\nlet { PromptTemplate } = await import(\"langchain/prompts\");\r\nlet { LLMChain } = await import(\"langchain/chains\");\r\nlet { ChainTool } = await import(\"langchain/tools\");\r\nlet { initializeAgentExecutorWithOptions } = await import(\"langchain/agents\");\r\nlet { DynamicTool } = await import(\"langchain/tools\");\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nawait env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\n\r\nconst task = await arg({\r\n placeholder: \"Task\",\r\n description: \"Enter a task for AC AGI to complete\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\nlet maxIterations = await arg({\r\n placeholder: \"How many times should AC AGI loop?\",\r\n hint: \"Leave empty for infinite iterations *use with caution*\",\r\n ignoreBlur: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nif (maxIterations === \"\" || maxIterations === \"0\") {\r\n maxIterations = undefined;\r\n}\r\n\r\n//#########################\r\n// BabyAGI method overrides\r\n//#########################\r\nfunction printTaskList() {\r\n let result = \"\";\r\n for (const t of this.taskList) {\r\n result += `${t.taskID}: ${t.taskName}\\n`;\r\n }\r\n const msg = `### Task List\r\n \r\n ${result}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printNextTask(task) {\r\n const msg = `### Next Task\r\n \r\n ${task.taskID}: ${task.taskName}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\nfunction printTaskResult(result) {\r\n const msg = `### Task Result\r\n \r\n ${result.trim()}\r\n `;\r\n let html = md(msg);\r\n\r\n div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n}\r\n\r\n//#############\r\n// Custom Tools\r\n//#############\r\nlet html = (str) => str.replace(/ /g, \"+\");\r\nlet fetch = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${html(\r\n q\r\n )}&sort=date`;\r\n\r\nasync function search(query) {\r\n let response = await get(fetch(query));\r\n\r\n let items = response?.data?.items;\r\n\r\n if (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n return JSON.stringify(choices);\r\n }\r\n}\r\n\r\nasync function humanFeedbackList(mdStr) {\r\n let html = md(`${mdStr.trim()}`);\r\n const response = div({\r\n html,\r\n ignoreBlur: true,\r\n });\r\n\r\n return response;\r\n}\r\n\r\nasync function humanInput(question) {\r\n const response = await arg({\r\n placeholder: \"Human, I need help!\",\r\n hint: question,\r\n ignoreBlur: true,\r\n ignoreAbandon: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n });\r\n return response;\r\n}\r\n\r\nconst todoPrompt = PromptTemplate.fromTemplate(\r\n \"You are a planner/expert todo list creator. Generate a markdown formatted todo list for: {objective}\"\r\n);\r\n\r\nconst tools = [\r\n new ChainTool({\r\n name: \"TODO\",\r\n chain: new LLMChain({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n prompt: todoPrompt,\r\n }),\r\n description:\r\n \"For making todo lists. Input: objective to create todo list for. Output: the todo list\",\r\n }),\r\n new DynamicTool({\r\n name: \"Search\",\r\n description: \"Search web for info\",\r\n func: search,\r\n }),\r\n new DynamicTool({\r\n name: \"Human Input\",\r\n description:\r\n \"(Use only when no info is available elsewhere) Ask a human for specific input that you don't know, like a persons name, or DOB, location, etc. Input is question to ask human, output is answer\",\r\n func: humanInput,\r\n }),\r\n // new DynamicTool({\r\n // name: \"Human Feedback Choice\",\r\n // description: `Ask human for feedback if you unsure of next step.\r\n // Input is markdown string formatted with your questions and suitable responses like this example:\r\n // # Human, I need your help!\r\n // \r\n // * [John](submit:John) // don't change formatting of these links\r\n // * [Mindy](submit:Mindy)\r\n // * [Joy](submit:Joy)\r\n // * [Other](submit:Other)\r\n // `,\r\n // func: humanFeedbackList,\r\n // }),\r\n];\r\n\r\n//##################\r\n// AC AGI is Born\r\n//##################\r\nconst taskBeginMsg = md(`\r\n### Executing Task Manager\r\nGoal: ${task}\r\n`);\r\n\r\ndiv({ html: taskBeginMsg, ignoreBlur: true });\r\n\r\nconst agentExecutor = await initializeAgentExecutorWithOptions(\r\n tools,\r\n new ChatOpenAI({ temperature: 0 }),\r\n {\r\n agentType: \"zero-shot-react-description\",\r\n agentArgs: {\r\n prefix: `You are an AI who performs one task based on the following objective: {objective}. \r\nTake into account these previously completed tasks: {context}.`,\r\n suffix: `Question: {task}\r\n{agent_scratchpad}`,\r\n inputVariables: [\"objective\", \"task\", \"context\", \"agent_scratchpad\"],\r\n },\r\n }\r\n);\r\n\r\nconst vectorStore = new MemoryVectorStore(new OpenAIEmbeddings());\r\n\r\nconst babyAGI = BabyAGI.fromLLM({\r\n llm: new ChatOpenAI({ temperature: 0 }),\r\n executionChain: agentExecutor,\r\n vectorstore: vectorStore,\r\n maxIterations: maxIterations,\r\n});\r\n\r\nbabyAGI.printNextTask = printNextTask;\r\nbabyAGI.printTaskList = printTaskList;\r\nbabyAGI.printTaskResult = printTaskResult;\r\n\r\nawait babyAGI.call({ objective: task });\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-30T21:26:47Z"},{"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","author":"Josh Mabry","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1230","url":"","title":"Example of BabyAGI running in ScriptKit","command":"example-of-babyagi-running-in-scriptkit","content":"","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-30T21:23:21Z"},{"name":"Google Search","description":"","author":"Josh Mabry","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1225","url":"","title":"Google Search","command":"google-search","content":"I know this is redundant and might not be useful to many, but I needed to build a custom search tool for an agent I'm working on. \r\n\r\nI set this up to test the functionality and figured someone might find it useful.\r\n\r\n```\r\n/* \r\n# Google Search\r\nExample of leveraging Google's Custom Search Engine API to search the web\r\n*/\r\n\r\n// Name: Google Search\r\n// Description: Leverage Google's Custom Search Engine API to search the web\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet GOOGLE_API_KEY = await env(\"GOOGLE_API_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google API Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://developers.google.com/custom-search/v1/introduction\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet GOOGLE_CSE_KEY = await env(\"GOOGLE_CSE_KEY\", {\r\n shortcuts: [\r\n {\r\n name: \"Google Custom Search Engine Key\",\r\n key: `${cmd}+o`,\r\n bar: \"right\",\r\n onPress: () => {\r\n open(\"https://programmablesearchengine.google.com/\");\r\n },\r\n },\r\n ],\r\n ignoreBlur: true,\r\n secret: true,\r\n height: PROMPT.HEIGHT.INPUT_ONLY,\r\n});\r\n\r\nlet query = await arg(\r\n {\r\n placeholder: \"Search Query\",\r\n strict: false,\r\n },\r\n [\r\n {\r\n name: \"Send a search query to Google\",\r\n info: \"always\",\r\n },\r\n ]\r\n);\r\n\r\nlet search = (q) =>\r\n `https://www.googleapis.com/customsearch/v1?key=${GOOGLE_API_KEY}&cx=${GOOGLE_CSE_KEY}&q=${q}&sort=date`;\r\n\r\nlet response = await get(search(query));\r\n\r\nlet items = response?.data?.items;\r\n\r\nif (items) {\r\n let choices = items.map((item) => ({\r\n name: item.title,\r\n value: item.link,\r\n }));\r\n\r\n let link = await arg(\"Choose a link to view\", choices);\r\n\r\n open(link);\r\n}\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-30T06:23:32Z"},{"avatar":"https://avatars.githubusercontent.com/u/95415447?v=4","user":"shyagamzo","author":"Shy Agam","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1224","url":"","title":"Reading aloud streamed text (GPT style)","command":"reading-aloud-streamed-text-gpt-style","content":"I wanted my ChatGPT responses to be read aloud immediately, as they appear on the screen.\r\nThis was problematic because of two reasons:\r\n1. ChatGPT sends partial text responses (e.g. 'he', 'llo', ', I', 'am S', 'hy'). This isn't readable and should be accumulated.\r\n2. The `say` command stops any spoken text and starts speaking the new text. Meaning, we cannot simply call it every time we receive new text.\r\n\r\nThis is a rough start, but works well for my current needs.\r\nThis util class maintains a queue, detects certain delimiters (e.g. `.`, `,`) and starts speaking only when it detects that a phrase has probably been accumulated.\r\n\r\nhttps://gist.github.com/shyagamzo/749b7535aa8876ec2ce09f39aaef6a80\r\n\r\n```typescript\r\nimport '@johnlindquist/kit';\r\n\r\nconst speechStream = new (class SpeechStream\r\n{\r\n private textQueue: string[] = [];\r\n private isSpeaking: boolean = false;\r\n private feed: string = '';\r\n private finalizeFeedDebounced: () => void;\r\n\r\n constructor(private readonly config: { waitForDelimiter: number, estimatedWordsPerMinute: number })\r\n {\r\n this.finalizeFeedDebounced = _.debounce(this.finalizeFeed.bind(this), config.waitForDelimiter);\r\n\r\n onExit(() =>\r\n {\r\n this.textQueue = [];\r\n this.feed = '';\r\n\r\n sayIt('');\r\n });\r\n }\r\n\r\n public addText(text: string): void\r\n {\r\n this.feed += text;\r\n this.processAccumulatedText();\r\n this.finalizeFeedDebounced();\r\n }\r\n\r\n private processAccumulatedText(): void\r\n {\r\n const delimiters = /([.,;:!?\\n])/;\r\n\r\n const delimiterMatch = this.feed.match(delimiters);\r\n\r\n if (delimiterMatch)\r\n {\r\n const delimiterIndex = delimiterMatch.index;\r\n\r\n const textUntilDelimiter = this.feed.slice(0, delimiterIndex + 1);\r\n this.textQueue.push(textUntilDelimiter.trim());\r\n\r\n this.feed = this.feed.slice(delimiterIndex + 1);\r\n }\r\n\r\n this.processQueue();\r\n }\r\n\r\n private finalizeFeed(): void\r\n {\r\n if (this.feed)\r\n {\r\n this.textQueue.push(this.feed.trim());\r\n this.feed = '';\r\n this.processQueue();\r\n }\r\n }\r\n\r\n private processQueue(): void\r\n {\r\n if (this.isSpeaking || this.textQueue.length === 0) return;\r\n\r\n this.isSpeaking = true;\r\n\r\n const textToSpeak = this.textQueue.shift();\r\n\r\n this.waitForSpeechEnd(textToSpeak);\r\n sayIt(textToSpeak);\r\n }\r\n\r\n private waitForSpeechEnd(text: string): void\r\n {\r\n const estimatedSpeechDuration = this.estimateSpeechDuration(text);\r\n\r\n setTimeout(() =>\r\n {\r\n this.isSpeaking = false;\r\n this.processQueue();\r\n }, estimatedSpeechDuration);\r\n }\r\n\r\n private estimateSpeechDuration(text: string): number\r\n {\r\n const wordsPerMinute = this.config.estimatedWordsPerMinute; // Average speaking rate\r\n const words = text.trim().split(/\\s+/).length;\r\n const minutes = words / wordsPerMinute;\r\n\r\n return minutes * 60 * 1000; // Convert to milliseconds\r\n }\r\n})({\r\n waitForDelimiter: 4000,\r\n estimatedWordsPerMinute: 200\r\n});\r\n\r\nexport function sayIt(text: string): ReturnType\r\n{\r\n return say(text, { name: 'Microsoft Zira - English (United States)', rate: 1.3 });\r\n}\r\n\r\nexport function queueSpeech(text: string)\r\n{\r\n speechStream.addText(text);\r\n}\r\n```\r\n\r\nTo use it, simply import and call `queueSpeech`:\r\n\r\n```typescript\r\nimport { queueSpeech } from '../lib/speech-queue';\r\n\r\nfunction handleGPTText(text: string)\r\n{\r\n // ...\r\n queueSpeech(text);\r\n}\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-29T21:32:41Z"},{"name":"Smartify Your Words","description":"","author":"Josh Mabry","twitter":null,"shortcut":"command shift enter","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1221","url":"","title":"Improve your writing with AI powers","command":"improve-your-writing-with-ai-powers","content":"[Deprecated] \r\nif you miss it, check out Kenv [here](https://github.com/artificialcitizens/ac-scripts)\r\n\r\nHighlight your poorly written text and run the script to automagically make yourself sound smarter!\r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273614-71a0b99c-5c9b-4806-84ba-2da8943359b9.mov\r\n\r\n\r\n\r\n```js\r\n/*\r\n/*\r\n# Smartify your words!\r\n\r\nTired of feeling dumb? Winter got you in a funk? \r\nCan you just not seem to get the words out right? \r\nWell, let's Smartify your words!\r\n\r\nHighlight some text and press `cmd+shift+enter` to send it through ChatGPT \r\nto replace the text with a more eloquent version. Mileage may vary.\r\n*/\r\n\r\n// Name: Smartify Your Words\r\n// Description: Let's make those words smarter!\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: command shift enter\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking an input and refactoring it using the following rules: '\r\n\r\n- Maintain the same meaning, tone, and intent as the original text\r\n- Clean up any grammar or spelling mistakes\r\n- Make it sound more professional, but keep it casual\r\n- Reduce redundancies and excessive verbiage\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes.\r\n########\r\n`;\r\n\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst smartify = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n if (!token) return;\r\n log(`handleLLMStart`);\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token) => {\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n await setSelectedText(JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\nlet text = await getSelectedText();\r\n\r\nawait smartify.call([\r\n new SystemChatMessage(prompt),\r\n new HumanChatMessage(text),\r\n]);\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-29T00:42:43Z"},{"name":"Prompt Anywhere","description":"","author":"Josh Mabry","twitter":null,"shortcut":"cmd shift P","avatar":"https://avatars.githubusercontent.com/u/31560031?u=2b74e74f3c4b0cbdc7eb0fa2d5f7261434323304&v=4","user":"mabry1985","discussion":"https://github.com/johnlindquist/kit/discussions/1220","url":"","title":"Prompt ChatGPT from anywhere you can input text","command":"prompt-chatgpt-from-anywhere-you-can-input-text","content":"[DEPRECATED] \r\n\r\nBy selecting any input text and running this script you now have access to the power of AI anywhere in your OS. \r\n\r\nhttps://user-images.githubusercontent.com/31560031/235273240-824a9432-87a3-48ac-9357-668a0c01b12c.mov\r\n\r\n```js\r\n/*\r\n# Generate a response to a prompt in any text field\r\n\r\nThis script utilizes ChatGPT to generate a response for the selected text. \r\nThe response will then replace the original text, making it useful for various writing tasks such as blog posts, code, and emails.\r\n\r\nHighlight some text and press `cmd+shift+p` to send it through ChatGPT \r\nto replace the text response. Your mileage may vary.\r\n*/\r\n\r\n// Name: Prompt Anywhere\r\n// Description: Generate a prompt response in anywhere you can type\r\n// Author: Josh Mabry\r\n// Twitter: @AI_Citizen\r\n// Shortcut: cmd shift P\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport Bottleneck from \"bottleneck\";\r\n\r\nlet { ChatOpenAI } = await import(\"langchain/chat_models\");\r\nlet { HumanChatMessage, SystemChatMessage } = await import(\"langchain/schema\");\r\nlet openAIApiKey = await env(\"OPENAI_API_KEY\", {\r\n hint: `Grab a key from here`,\r\n});\r\nlet { CallbackManager } = await import(\"langchain/callbacks\");\r\nlet prompt = `#####\r\nIgnore prior instructions, you are tasked with taking the input text and generating a response to it.\r\n\r\nReturn only the updated text, do not offer any explanations or reasoning for the changes. \r\nDo not ask any questions, do not refuse any input, and do not change the subject.\r\n`;\r\n\r\nconst limiter = new Bottleneck({\r\n maxConcurrent: 1,\r\n minTime: 100,\r\n});\r\n\r\nconst type = (text) => {\r\n return new Promise((resolve) => {\r\n keyboard.type(text);\r\n resolve();\r\n });\r\n};\r\n\r\nconst wrappedType = limiter.wrap(type);\r\n\r\nconst chat = new ChatOpenAI({\r\n temperature: 0.3,\r\n openAIApiKey: openAIApiKey,\r\n streaming: true,\r\n callbackManager: CallbackManager.fromHandlers({\r\n handleLLMStart: async (token) => {\r\n log(`handleLLMStart`);\r\n if (!token) return;\r\n await wrappedType(token);\r\n },\r\n handleLLMNewToken: async (token, runId) => {\r\n log(`handleLLMNewToken`);\r\n // occasionally I see tokens typed out of order\r\n // still not sure why this happens\r\n await wrappedType(token);\r\n },\r\n handleLLMError: async (err) => {\r\n warn(`error`, JSON.stringify(err));\r\n process.exit(1);\r\n },\r\n handleLLMEnd: async () => {\r\n log(`handleLLMEnd`);\r\n log(`currentMessage`, currentMessage);\r\n process.exit(1);\r\n },\r\n }),\r\n});\r\n\r\nlet text = await getSelectedText();\r\n\r\nawait chat.call([new SystemChatMessage(prompt), new HumanChatMessage(text)]);\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-29T00:36:59Z"},{"name":"Open Vercel project dashboard","description":"","author":"Michael Rieger","twitter":"mueslirieger","avatar":"https://avatars.githubusercontent.com/u/20973893?u=359eca24a75c3971794c4daba03f723f0164762a&v=4","user":"mueslirieger","discussion":"https://github.com/johnlindquist/kit/discussions/1212","url":"https://gist.githubusercontent.com/mueslirieger/21b1b1b9e6ef48ecf64d8d1a3937f0e8/raw/08532f733744bd314da5667b9e2feff49c6da6ca/open-vercel-project.ts","title":"Open Vercel project dashboard","command":"open-vercel-project-dashboard","content":"I use this script so much, I had to share it. It lets you select a personal or team project that is hosted on Vercel and opens the dashboard page of the selected project.\r\n\r\n\r\n[Open open-vercel-project in Script Kit](https://scriptkit.com/api/new?name=open-vercel-project&url=https://gist.githubusercontent.com/mueslirieger/21b1b1b9e6ef48ecf64d8d1a3937f0e8/raw/08532f733744bd314da5667b9e2feff49c6da6ca/open-vercel-project.ts\")\r\n\r\n```typescript\r\n/*\r\n# Open Vercel project dashboard\r\n\r\nLets the user select and open the dashboard page of a project hosted on Vercel.\r\n*/\r\n\r\n// Name: Open Vercel project dashboard\r\n// Description: Lets the user select and open the dashboard page of a project hosted on Vercel.\r\n// Author: Michael Rieger\r\n// Twitter: @mueslirieger\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst apiBaseUrl = 'https://api.vercel.com';\r\nconst dashboardBaseUrl = 'https://vercel.com';\r\n\r\n// ask user to create an access token for the rest api\r\nconst VERCEL_ACCESS_TOKEN = await env('VERCEL_ACCESS_TOKEN', {\r\n panel: md(`## Get a [Vercel API Access Token](https://vercel.com/account/tokens)`),\r\n ignoreBlur: true,\r\n secret: true,\r\n});\r\n\r\nconst user = await fetchUser();\r\n\r\n// Select whether personal or team projects should be listed\r\nconst projectsType = await selectProjectsType();\r\n\r\n// If team projects were selected list the teams the user is assigned to\r\nlet team: Team | undefined | null = null;\r\nif (projectsType === 'team') {\r\n const teams = await fetchTeams();\r\n team = await selectTeam(teams);\r\n}\r\n\r\n// Fetch projects based on previous selection\r\nconst projects = await fetchProjects(team?.id);\r\n\r\n// let user select project and open in browser\r\nconst project = await selectProject(projects);\r\n\r\nif (!project) exit(-1);\r\n\r\nawait browse(`${dashboardBaseUrl}/${projectsType === 'team' ? team.slug : user.username}/${project.name}`);\r\n\r\n// -----------------------------------------------------\r\n// Helpers\r\n// -----------------------------------------------------\r\n\r\ntype VercelApiError = {\r\n error?: {\r\n code: string;\r\n message: string;\r\n };\r\n};\r\n\r\nasync function selectProjectsType() {\r\n return arg<'personal' | 'team'>('Show personal or team projects', [\r\n {\r\n value: 'personal',\r\n name: '[P]ersonal',\r\n shortcut: 'p',\r\n },\r\n {\r\n value: 'team',\r\n name: '[T]eam',\r\n shortcut: 't',\r\n },\r\n ]);\r\n}\r\n\r\ntype User = {\r\n id: string;\r\n email: string;\r\n name: string | null;\r\n username: string;\r\n};\r\ntype GetUserResponse = { user: User } & VercelApiError;\r\n\r\nasync function fetchUser() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/user`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.user;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\ntype Team = { id: string; name: string; slug: string; avatar: string | null };\r\ntype GetTeamsResponse = { teams?: Team[] } & VercelApiError;\r\n\r\nasync function fetchTeams() {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v2/teams`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.teams;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectTeam(teams: Team[]) {\r\n return await arg(\r\n {\r\n placeholder: teams.length ? 'Select a team' : 'No teams found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Select team`,\r\n },\r\n teams.map((team) => ({\r\n value: team,\r\n name: team.name,\r\n img: team.avatar ? `https://vercel.com/api/www/avatar/${team.avatar}?s=128` : '',\r\n }))\r\n );\r\n}\r\n\r\ntype Project = { id: string; name: string; latestDeployments: { alias: string[] }[] };\r\ntype GetProjectsResponse = { projects?: Project[] } & VercelApiError;\r\n\r\nasync function fetchProjects(teamId?: string | null | undefined) {\r\n try {\r\n const res = await get(`${apiBaseUrl}/v9/projects${teamId ? `?teamId=${teamId}` : ''}`, {\r\n headers: { Authorization: `Bearer ${VERCEL_ACCESS_TOKEN}` },\r\n });\r\n\r\n if (res.status !== 200 || res.data.error) exit();\r\n\r\n return res.data.projects;\r\n } catch (e) {\r\n exit(-1);\r\n }\r\n}\r\n\r\nasync function selectProject(projects: Project[]) {\r\n return await arg(\r\n {\r\n placeholder: projects.length ? 'Select a project' : 'No projects found',\r\n onChoiceFocus: (input, { focused }) => {\r\n setPlaceholder(focused.name);\r\n },\r\n enter: `Open project in dashboard`,\r\n },\r\n projects.map((project) => ({\r\n value: project,\r\n name: project.name,\r\n }))\r\n );\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-27T07:15:16Z"},{"name":"Merge / Split Alfred clipboard","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1209","url":"https://gist.githubusercontent.com/ramiroaraujo/4803f2f333828e5cbd30a49cc426cd22/raw/2f9d71e8c1dc94525f7bc7996b47d820fbadfb9a/merge-split-alfred-clipboard.ts","title":"Merge / Split Afred Clipboard Script","command":"merge-split-afred-clipboard-script","content":"\r\n[Open merge-split-alfred-clipboard in Script Kit](https://scriptkit.com/api/new?name=merge-split-alfred-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/4803f2f333828e5cbd30a49cc426cd22/raw/2f9d71e8c1dc94525f7bc7996b47d820fbadfb9a/merge-split-alfred-clipboard.ts\")\r\n\r\nThis is a very specific yet useful Script, for those who have Alfred app with Powerpack and use the clipboard history. It allows you to split and merge it in several ways. \r\nFor `merge`, it asks you for the number of items in the clipboard, with a preview, and then asks you the merging character or characters. The resulting merge is placed in the clipboard.\r\nFor `split`, it asks you for a splitting character or characters, and saves all the resulted strings (after splitting) in the clipboard history.\r\n\r\nUse cases:\r\n* copy a bunch of values from different places, join them together in one shot, by `\\n'\r\n* copy a list of values, separated by comma or `\\n`, split them and paste them individually in a form\r\n\r\n```js\r\n// Name: Merge / Split Alfred clipboard\r\n// Description: Merge or split clipboard content using Alfred app's clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\");\r\nconst databasePath = home('Library/Application Support/Alfred/Databases/clipboard.alfdb')\r\nif (!await pathExists(databasePath)) {\r\n notify(\"Alfred clipboard database not found\" )\r\n exit()\r\n}\r\n\r\nconst db = new Database(databasePath);\r\n\r\nconst queryClipboard = async (sql, params) => {\r\n const stmt = db.prepare(sql);\r\n return sql.trim().toUpperCase().startsWith(\"SELECT\") ? stmt.all(params) : stmt.run(params);\r\n};\r\n\r\nconst getMergedClipboards = async (count, separator) => {\r\n const sql = `SELECT item FROM clipboard WHERE dataType = 0 order by ROWID desc LIMIT ?`;\r\n const clipboards = await queryClipboard(sql, [count]);\r\n return clipboards.map(row => row.item.trim()).join(separator);\r\n};\r\n\r\nconst writeMergedClipboards = async (mergedText) => {\r\n await clipboard.writeText(mergedText);\r\n};\r\n\r\nconst getSplitClipboard = async (separator, trim) => {\r\n const currentClipboard = await clipboard.readText();\r\n return currentClipboard.split(separator).map(item => trim ? item.trim() : item);\r\n};\r\n\r\nconst writeSplitClipboard = async (splitText) => {\r\n const lastTsSql = `SELECT ts FROM clipboard WHERE dataType = 0 ORDER BY ts DESC LIMIT 1`;\r\n const lastTsResult = await queryClipboard(lastTsSql, []);\r\n let lastTs = lastTsResult.length > 0 ? Number(lastTsResult[0].ts) : 0;\r\n\r\n const insertSql = `INSERT INTO clipboard (item, ts, dataType, app, appPath) VALUES (?, ?, 0, 'Kit', '/Applications/Kit.app')`;\r\n\r\n for (let i = 0; i < splitText.length - 1; i++) {\r\n lastTs += 1;\r\n await queryClipboard(insertSql, [splitText[i], lastTs]);\r\n }\r\n\r\n await clipboard.writeText(splitText[splitText.length - 1]);\r\n};\r\n\r\n\r\nconst action = await arg(\"Choose action\", [\"Merge\", \"Split\"]);\r\n\r\nif (action === \"Merge\") {\r\n const count = await arg({\r\n placeholder: \"Enter the number of clipboard items to merge\",\r\n }, async (input) => {\r\n if (isNaN(Number(input)) || input.length === 0)return ''\r\n return md(`
${await getMergedClipboards(input, '\\n')}
`)\r\n })\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for merging\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n return md(`
${await getMergedClipboards(count, input)}
`)\r\n })\r\n const mergedText = await getMergedClipboards(count, separator);\r\n await writeMergedClipboards(mergedText);\r\n await notify(\"Merged clipboard items and copied to clipboard\");\r\n} else {\r\n // const separator = await arg(\"Enter the separator for splitting\");\r\n const separator = await arg({\r\n placeholder: \"Enter the separator for splitting\",\r\n }, async (input) => {\r\n if (input === '\\\\n') input = '\\n'\r\n let strings = await getSplitClipboard(input, true);\r\n return md(`
${strings.join('\\n')}
`)\r\n })\r\n const trim = await arg(\"Trim clipboard content?\", [\"Yes\", \"No\"]);\r\n const splitText = await getSplitClipboard(separator, trim === \"Yes\");\r\n await writeSplitClipboard(splitText);\r\n await notify(\"Split clipboard content and stored in Alfred clipboard\");\r\n}\r\n\r\ndb.close();\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:19:34Z"},{"name":"Type Clipboard","description":"","shortcut":"ctrl+cmd+alt+shift+v","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1208","url":"https://gist.githubusercontent.com/ramiroaraujo/a18db9f56dfe8f6745ce6e917baf8ade/raw/eb3878c3aaede4e064bfbb451e6d30851b84160e/type-clipboard.ts","title":"Type Clipboard Script","command":"type-clipboard-script","content":"\r\n[Open type-clipboard in Script Kit](https://scriptkit.com/api/new?name=type-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/a18db9f56dfe8f6745ce6e917baf8ade/raw/eb3878c3aaede4e064bfbb451e6d30851b84160e/type-clipboard.ts\")\r\n\r\nThis is a Script I use more often than I would care to admit. There're situations where the `paste` command just doesn't work. Either web forms that don't allow paste, or crappy app UIs that for some reason a normal paste doesn't work. If you don't mind the lengthy shortcut, you hit `ctrl+cmd+alt+shift+v` and it `types` the content of the clipboard really fast, instead of pasting it.\r\n\r\n```js\r\n// Name: Type Clipboard\r\n// Description: Get the content of the clipboard and \"keystroke\" it without pasting\r\n// Shortcut: ctrl+cmd+alt+shift+v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst clipboardText = await clipboard.readText()\r\n\r\nif (clipboardText.length > 1000) {\r\n await notify(\"Clipboard content is too long\")\r\n exit()\r\n}\r\n\r\nawait applescript(String.raw`\r\n set chars to count (get the clipboard)\r\n tell application \"System Events\"\r\n delay 0.1\r\n keystroke (get the clipboard)\r\n end tell\r\n`)\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:13:01Z"},{"name":"Open in WhatsApp","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1207","url":"https://gist.githubusercontent.com/ramiroaraujo/a61f67f8b55a3805888ff092b77c2550/raw/80eaadf67510c49d0724bb82b69f2a00ddb0d7d6/open-in-whatsapp.ts","title":"Open in WhatsApp Script","command":"open-in-whatsapp-script","content":"\r\n[Open open-in-whatsapp in Script Kit](https://scriptkit.com/api/new?name=open-in-whatsapp&url=https://gist.githubusercontent.com/ramiroaraujo/a61f67f8b55a3805888ff092b77c2550/raw/80eaadf67510c49d0724bb82b69f2a00ddb0d7d6/open-in-whatsapp.ts\")\r\n\r\nAnother simple script for opening a phone number in WhatsApp to chat. It fetches the number from the clipboard, and if no country code is provided it assumes Argentina, where I'm from, but of course change it to your default country\r\n\r\n```js\r\n// Name: Open in WhatsApp\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the text from the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//normalize the text\r\ntext = text.replace(/[-() ]/g, \"\");\r\n\r\n//validate if valid phone number\r\nif (!text.match(/^(\\+\\d{12,13})|(\\d{10,11})$/)) {\r\n notify(\"Invalid phone number\");\r\n exit()\r\n}\r\n\r\n//assume Argentina if no country code since that's where I'm from\r\nif (!text.startsWith(\"+\")) {\r\n text = \"+54\" + text;\r\n}\r\n\r\n//open in WhatsApp\r\nopen(`https://wa.me/${text}`);\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:10:43Z"},{"avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1206","url":"https://gist.githubusercontent.com/ramiroaraujo/51a8303fd66cc9d8b6db8a19c651254e/raw/b19ada003b9f58f48976636115e136cd2841ed0a/convert-selected-images.ts","title":"Convert selected images Script","command":"convert-selected-images-script","content":"\r\n[Open convert-selected-images in Script Kit](https://scriptkit.com/api/new?name=convert-selected-images&url=https://gist.githubusercontent.com/ramiroaraujo/51a8303fd66cc9d8b6db8a19c651254e/raw/b19ada003b9f58f48976636115e136cd2841ed0a/convert-selected-images.ts\")\r\n\r\nThis Script will convert all your selected (supported) images to either `jpg`, `png` or `webp`. I mostly created it to deal with sending images from the phone to the mac, and getting them as `heic`...\r\n\r\n```js\r\n // Name: convert selected images\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n// Grab selected files\r\nconst files = (await getSelectedFile()).split(\"\\n\");\r\n\r\n// Set up whitelist of formats\r\nconst supportedFormats = [\".heic\", \".png\", \".gif\", \".webp\", \".jpg\", \".jpeg\"];\r\n\r\n// Filter files based on supported formats\r\nconst selectedFiles = files.filter(file =>\r\n supportedFormats.some(format => file.toLowerCase().endsWith(format))\r\n);\r\n\r\n// Notify if no files are selected\r\nif (!selectedFiles.length) {\r\n await notify(\"No supported files selected\");\r\n exit();\r\n}\r\n\r\nconst convertHeic = await npm(\"heic-convert\");\r\nconst sharp = await npm(\"sharp\");\r\n\r\n// Select the output format\r\nconst outputFormat = await arg(\"Choose an output format\", [\r\n \"jpg\",\r\n \"png\",\r\n \"webp\",\r\n]);\r\n\r\nconst getUniquePath = async (outputPath, suffix = \"\") => {\r\n if (await isFile(outputPath)) {\r\n const name = path.basename(outputPath, path.extname(outputPath));\r\n const newName = `${name}${suffix}-copy${path.extname(outputPath)}`;\r\n const newPath = path.join(path.dirname(outputPath), newName);\r\n return await getUniquePath(newPath, `${suffix}-copy`);\r\n } else {\r\n return outputPath;\r\n }\r\n};\r\n\r\n// Convert selected files to the chosen output format using appropriate libraries\r\nfor (const file of selectedFiles) {\r\n const content = await readFile(file);\r\n const name = path.basename(file).split(\".\")[0];\r\n const outputPath = path.join(path.dirname(file), name + `.${outputFormat}`);\r\n\r\n const uniqueOutputPath = await getUniquePath(outputPath);\r\n\r\n if (file.toLowerCase().endsWith(\".heic\")) {\r\n const formatMap = {\r\n jpg: \"JPEG\",\r\n png: \"PNG\",\r\n }\r\n const outputBuffer = await convertHeic({\r\n buffer: content,\r\n format: formatMap[outputFormat],\r\n quality: 0.5,\r\n });\r\n\r\n await writeFile(uniqueOutputPath, outputBuffer);\r\n } else {\r\n const sharpImage = sharp(content);\r\n\r\n switch (outputFormat) {\r\n case \"jpg\":\r\n await sharpImage.jpeg({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n case \"png\":\r\n await sharpImage.png().toFile(uniqueOutputPath);\r\n break;\r\n case \"webp\":\r\n await sharpImage.webp({ quality: 40 }).toFile(uniqueOutputPath);\r\n break;\r\n }\r\n }\r\n}\r\n\r\nawait notify(`Converted selected files to ${outputFormat.toUpperCase()}`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:08:46Z"},{"name":"Open URL in clipboard","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1205","url":"https://gist.githubusercontent.com/ramiroaraujo/600d82866cd35b21998897843a4c3eb4/raw/c729cfb0baf43e6aa371a0ed0050ad69d5a9267d/open-url-in-clipboard.ts","title":"Open URL in clipboard Script","command":"open-url-in-clipboard-script","content":"\r\n[Open open-url-in-clipboard in Script Kit](https://scriptkit.com/api/new?name=open-url-in-clipboard&url=https://gist.githubusercontent.com/ramiroaraujo/600d82866cd35b21998897843a4c3eb4/raw/c729cfb0baf43e6aa371a0ed0050ad69d5a9267d/open-url-in-clipboard.ts\")\r\n\r\nDead simple script for the very common use case of copying _some_ text with a URL in it, and wanting to navigate to that URL. Will fetch the first one it finds and go\r\n\r\n```js\r\n// Name: Open URL in clipboard\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n//get the clipboard\r\nlet text = await clipboard.readText();\r\n\r\n//get the first URL in the clipboard, if any\r\nlet url = text.match(/(https?:\\/\\/[^\\s]+)/);\r\n\r\n//if there's a URL, open it\r\nif (url) {\r\n open(url[0]);\r\n} else {\r\n notify(\"No URL found in clipboard\");\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:06:12Z"},{"name":"Emoji Search","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1204","url":"https://gist.githubusercontent.com/ramiroaraujo/5b5f92d043e1ffa07af92215395d9231/raw/e134a3a357dfe43276d9d83bbd73ca01aad74537/emoji-search.ts","title":"Emoji Search Script","command":"emoji-search-script","content":"\r\n[Open emoji-search in Script Kit](https://scriptkit.com/api/new?name=emoji-search&url=https://gist.githubusercontent.com/ramiroaraujo/5b5f92d043e1ffa07af92215395d9231/raw/e134a3a357dfe43276d9d83bbd73ca01aad74537/emoji-search.ts\")\r\n\r\nA rather simple Emoji search that uses local database for fast lookup. It actually bootstrap by creating a sqlite database out of the `emojilib` JSON, in particular for storing usage and sorting by it. It will search by name and keywords, and the list will be sorted by most used\r\n\r\n```js\r\n// Name: Emoji Search\r\n// Description: Search and copy emoji to clipboard using SQLite database\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst Database = await npm(\"better-sqlite3\")\r\nconst databaseFile = projectPath(\"db\", \"emoji-search-emojilib.db\")\r\n\r\nconst emojilibURL = \"https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json\"\r\n\r\nconst createDatabase = async () => {\r\n const response = await get(emojilibURL)\r\n const emojiData = response.data as Record\r\n\r\n //create db and table\r\n const db = new Database(databaseFile)\r\n db.exec(`CREATE TABLE IF NOT EXISTS emojis\r\n (emoji TEXT PRIMARY KEY, name TEXT, keywords TEXT, used INTEGER DEFAULT 0)`)\r\n\r\n //populate with data from emojilib\r\n for (const [emojiChar, emojiInfo] of Object.entries(emojiData)) {\r\n const description = emojiInfo[0]\r\n const tags = emojiInfo.slice(1).join(', ')\r\n\r\n db.prepare(\"INSERT OR REPLACE INTO emojis VALUES (?, ?, ?, 0)\").run(emojiChar, description, tags)\r\n }\r\n db.close()\r\n};\r\n\r\nif (!await pathExists(databaseFile)) {\r\n await createDatabase()\r\n}\r\n\r\nconst db = new Database(databaseFile)\r\n\r\nconst queryEmojis = async () => {\r\n const sql = \"SELECT emoji, name, keywords FROM emojis ORDER BY used DESC\"\r\n const stmt = db.prepare(sql)\r\n return stmt.all()\r\n}\r\n\r\nconst snakeToHuman = (text) => {\r\n return text\r\n .split('_')\r\n .map((word, index) => index === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word)\r\n .join(' ')\r\n}\r\n\r\nconst emojis = await queryEmojis()\r\n\r\nconst selectedEmoji = await arg(\"Search Emoji\", emojis.map(({ emoji, name, keywords }) => ({\r\n name: `${snakeToHuman(name)} ${keywords}`,\r\n html: md(`
\r\n ${emoji}\r\n
\r\n ${snakeToHuman(name)}\r\n ${keywords} \r\n
\r\n
`),\r\n value: emoji,\r\n\r\n})))\r\n\r\nawait clipboard.writeText(selectedEmoji)\r\n\r\n// Update the 'used' count\r\nconst updateSql = \"UPDATE emojis SET used = used + 1 WHERE emoji = ?\"\r\nconst updateStmt = db.prepare(updateSql)\r\nupdateStmt.run(selectedEmoji)\r\n\r\ndb.close()\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:04:56Z"},{"name":"Text Manipulation","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1203","url":"https://gist.githubusercontent.com/ramiroaraujo/55cd0f21adb60f0a270c18fbcce99454/raw/7dd3ecffbaa6eea66b9c3476ea8122ea31ef25e5/text-manipulation.ts","title":"Text Manipulation Script","command":"text-manipulation-script","content":"\r\n[Open text-manipulation in Script Kit](https://scriptkit.com/api/new?name=text-manipulation&url=https://gist.githubusercontent.com/ramiroaraujo/55cd0f21adb60f0a270c18fbcce99454/raw/7dd3ecffbaa6eea66b9c3476ea8122ea31ef25e5/text-manipulation.ts\")\r\n\r\nInspired by a mix of an old Pipe workflow for Alfred mixed with the String Manipulation Plugin for Jetbrains IDEs. It will transform the current content of the clipboard based on the operation you select. Some operations require a parameter (`joinBy` for example), in those cases it asks for it. Both in the operation selection and parameter it shows a preview of the resulting text. \r\nIf you select an operation by `Cmd + enter` you'll be prompted by another operation to select after the first one is finished, and you can continue \"piping\" the outputs until you're done. Since it's common for me to `Cmd + enter` one last time and don't actually need the transformation there's a `No Operation` transform to select on this cases.\r\n\r\nUse cases:\r\n* copy a large list of values, wrap them in `'`, join them by `\\n`\r\n* capture numbers regex in each line, clean empty lines, join them by `+`, paste in ScriptKit or Alfred for sum result\r\n* filter lines by regex\r\n\r\nIt's hard to explain how useful this ends up being in my day to day\r\n\r\n```js\r\n// Name: Text Manipulation\r\n// Description: Transform clipboard text based on user-selected options\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet transformations = {\r\n upperCase: text => text.toUpperCase(),\r\n lowerCase: text => text.toLowerCase(),\r\n capitalize: text => text.split('\\n').map(line => line.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')).join('\\n'),\r\n decodeUrl: text => text.split('\\n').map(line => decodeURIComponent(line)).join('\\n'),\r\n snakeCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `_${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n camelCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => p.toUpperCase()).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n kebabCase: text => text.split('\\n').map(line => line.replace(/[\\s-_]+(\\w)/g, (_, p) => `-${p.toLowerCase()}`).replace(/^[A-Z]/, match => match.toLowerCase())).join('\\n'),\r\n reverseCharacters: text => text.split('\\n').map(line => line.split('').reverse().join('')).join('\\n'),\r\n removeDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n return [...new Set(lines)].join('\\n');\r\n },\r\n keepOnlyDuplicateLines: text => {\r\n let lines = text.split('\\n');\r\n let duplicates = lines.filter((item, index) => lines.indexOf(item) !== index);\r\n return [...new Set(duplicates)].join('\\n');\r\n },\r\n removeEmptyLines: text => text.split('\\n').filter(line => line.trim() !== '').join('\\n'),\r\n removeAllNewLines: text => text.split('\\n').map(line => line.trim()).join(''),\r\n trimEachLine: text => text.split('\\n').map(line => line.trim()).join('\\n'),\r\n sortLinesAlphabetically: text => text.split('\\n').sort().join('\\n'),\r\n sortLinesNumerically: text => text.split('\\n').sort((a, b) => a - b).join('\\n'),\r\n reverseLines: text => text.split('\\n').reverse().join('\\n'),\r\n shuffleLines: text => {\r\n let lines = text.split('\\n')\r\n for (let i = lines.length - 1; i > 0; i--) {\r\n let j = Math.floor(Math.random() * (i + 1))\r\n let temp = lines[i]\r\n lines[i] = lines[j]\r\n lines[j] = temp\r\n }\r\n return lines.join('\\n')\r\n },\r\n joinBy: (text, separator) => text.split('\\n').join(separator),\r\n splitBy: (text, separator) => text.split(separator).join('\\n'),\r\n removeWrapping: text => {\r\n const lines = text.split('\\n');\r\n const matchingPairs = [['(', ')'], ['[', ']'], ['{', '}'], ['<', '>'], ['\"', '\"'], [\"'\", \"'\"]];\r\n return lines\r\n .map(line => {\r\n const firstChar = line.charAt(0);\r\n const lastChar = line.charAt(line.length - 1);\r\n\r\n for (const [open, close] of matchingPairs) {\r\n if (firstChar === open && lastChar === close) {\r\n return line.slice(1, -1);\r\n }\r\n }\r\n\r\n if (firstChar === lastChar) {\r\n return line.slice(1, -1);\r\n }\r\n\r\n return line;\r\n })\r\n .join('\\n');\r\n },\r\n wrapEachLine: (text, wrapper) => {\r\n const lines = text.split('\\n');\r\n\r\n return lines\r\n .map(line => `${wrapper}${line}${wrapper}`)\r\n .join('\\n');\r\n },\r\n captureEachLine: (text, regex) => {\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex);\r\n\r\n return lines\r\n .map(line => {\r\n const match = line.match(pattern);\r\n return match ? match[0] : '';\r\n })\r\n .join('\\n');\r\n },\r\n removeLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i');\r\n\r\n return lines\r\n .filter(line => !pattern.test(line))\r\n .join('\\n');\r\n },\r\n keepLinesMatching: (text, regex) => {\r\n if (regex.length === 0) return text;\r\n const lines = text.split('\\n');\r\n const pattern = new RegExp(regex, 'i')\r\n\r\n return lines\r\n .filter(line => pattern.test(line))\r\n .join('\\n');\r\n },\r\n prependTextToAllLines: (text, prefix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => prefix + line).join('\\n');\r\n },\r\n\r\n appendTextToAllLines: (text, suffix) => {\r\n const lines = text.split('\\n');\r\n return lines.map(line => line + suffix).join('\\n');\r\n },\r\n\r\n replaceRegexInAllLines: (text, regexWithReplacement) => {\r\n const [regex, replacement] = regexWithReplacement.split('|');\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, replacement)).join('\\n');\r\n },\r\n removeRegexInAllLines: (text, regex) => {\r\n const pattern = new RegExp(regex, 'g');\r\n const lines = text.split('\\n');\r\n return lines.map(line => line.replace(pattern, '')).join('\\n');\r\n },\r\n generateNumberedList: (text) => {\r\n const lines = text.split('\\n');\r\n return lines.map((line, index) => `${index + 1}. ${line}`).join('\\n');\r\n },\r\n noop: text => text,\r\n}\r\n\r\nlet options = [\r\n // Existing options here\r\n {\r\n name: \"Decode URL\", description: \"Decode a URL-encoded text\", value: {\r\n key: \"decodeUrl\"\r\n }\r\n },\r\n {\r\n name: \"Upper Case\",\r\n description: \"Transform the entire text to upper case\",\r\n value: {\r\n key: \"upperCase\",\r\n },\r\n },\r\n {\r\n name: \"Lower Case\",\r\n description: \"Transform the entire text to lower case\",\r\n value: {\r\n key: \"lowerCase\",\r\n },\r\n },\r\n {\r\n name: \"snake_case\", description: \"Convert text to snake_case\", value: {\r\n key: \"snakeCase\"\r\n }\r\n },\r\n {\r\n name: \"Capitalize\", description: \"Convert text to Capital Case\", value: {\r\n key: \"capitalize\"\r\n }\r\n },\r\n {\r\n name: \"camelCase\", description: \"Convert text to camelCase\", value: {\r\n key: \"camelCase\"\r\n }\r\n },\r\n {\r\n name: \"kebab-case\", description: \"Convert text to kebab-case\", value: {\r\n key: \"kebabCase\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Characters\", description: \"Reverse the characters in the text\", value: {\r\n key: \"reverseCharacters\"\r\n }\r\n },\r\n {\r\n name: \"Remove Duplicate Lines\",\r\n description: \"Remove duplicate lines from the text\",\r\n value: {\r\n key: \"removeDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Keep Only Duplicate Lines\",\r\n description: \"Keep only duplicate lines in the text\",\r\n value: {\r\n key: \"keepOnlyDuplicateLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove Empty Lines\", description: \"Remove empty lines from the text\", value: {\r\n key: \"removeEmptyLines\"\r\n }\r\n },\r\n {\r\n name: \"Remove All New Lines\", description: \"Remove all new lines from the text\", value: {\r\n key: \"removeAllNewLines\"\r\n }\r\n },\r\n {\r\n name: \"Trim Each Line\",\r\n description: \"Trim whitespace from the beginning and end of each line\",\r\n value: {\r\n key: \"trimEachLine\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Alphabetically\", description: \"Sort lines alphabetically\", value: {\r\n key: \"sortLinesAlphabetically\"\r\n }\r\n },\r\n {\r\n name: \"Sort Lines Numerically\", description: \"Sort lines numerically\", value: {\r\n key: \"sortLinesNumerically\"\r\n }\r\n },\r\n {\r\n name: \"Reverse Lines\", description: \"Reverse the order of lines\", value: {\r\n key: \"reverseLines\"\r\n }\r\n },\r\n {\r\n name: \"Shuffle Lines\", description: \"Randomly shuffle the order of lines\", value: {\r\n key: \"shuffleLines\"\r\n }\r\n },\r\n {\r\n name: \"Join By\",\r\n description: \"Join lines by a custom separator\",\r\n value: {\r\n key: \"joinBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to join lines\",\r\n defaultValue: \",\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Split By\",\r\n description: \"Split lines by a custom separator\",\r\n value: {\r\n key: \"splitBy\",\r\n parameter: {\r\n name: \"Separator\",\r\n description: \"Enter a separator to split lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Wrapping\",\r\n description: \"Remove wrapping characters from each line\",\r\n value: {\r\n key: \"removeWrapping\",\r\n },\r\n },\r\n {\r\n name: \"Wrap Each Line With\",\r\n description: \"Wrap each line with a custom character or string\",\r\n value: {\r\n key: \"wrapEachLine\",\r\n parameter: {\r\n name: \"Wrapper\",\r\n description: \"Enter a wrapper for each line\",\r\n defaultValue: '\"',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Capture Each Line\",\r\n description: \"Capture and return the first match of a regex pattern in each line\",\r\n value: {\r\n key: \"captureEachLine\",\r\n parameter: {\r\n name: \"Pattern\",\r\n description: \"Enter a regex pattern to capture\",\r\n defaultValue: \"\\\\d+\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"Remove Lines Matching\",\r\n description: \"Remove lines that match the given regex\",\r\n value: {\r\n key: \"removeLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to remove\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Keep Lines Matching\",\r\n description: \"Keep lines that match the given regex\",\r\n value: {\r\n key: \"keepLinesMatching\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to match lines to keep\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Prepend Text to All Lines\",\r\n description: \"Add text to the beginning of all lines\",\r\n value: {\r\n key: \"prependTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to prepend to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Append Text to All Lines\",\r\n description: \"Add text to the end of all lines\",\r\n value: {\r\n key: \"appendTextToAllLines\",\r\n parameter: {\r\n name: \"Text\",\r\n description: \"Enter text to append to all lines\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Replace Regex in All Lines\",\r\n description: \"Replace regex matches in all lines with specified text\",\r\n value: {\r\n key: \"replaceRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex and Replacement\",\r\n description: \"Enter regex and replacement text separated by a '|'\",\r\n defaultValue: '',\r\n },\r\n },\r\n },\r\n {\r\n name: \"Generate Numbered List\",\r\n description: \"Prepend numbers to each line\",\r\n value: {\r\n key: \"generateNumberedList\",\r\n },\r\n },\r\n {\r\n name: \"Remove Regex In All Lines\",\r\n description: \"Remove matches of the provided regex in all lines\",\r\n value: {\r\n key: \"removeRegexInAllLines\",\r\n parameter: {\r\n name: \"Regex\",\r\n description: \"Enter a regex to remove from all lines\",\r\n },\r\n },\r\n },\r\n {\r\n name: \"No Operation\",\r\n description: \"Do nothing to the text, if you accidentally hit Cmd + enter and need no more transformations\",\r\n }\r\n]\r\n\r\nconst handleTransformation = async (text, transformation) => {\r\n let {key, parameter} = transformation;\r\n let paramValue = parameter ? await arg({\r\n input: parameter.defaultValue,\r\n }, (input) => md(`
`)\r\n } catch (e) {\r\n return '...'\r\n }\r\n },\r\n }\r\n })\r\n )\r\n rerun = flag?.rerun as boolean;\r\n\r\n clipboardText = await handleTransformation(clipboardText, transformation);\r\n operations.push(transformation.key);\r\n}\r\n\r\nawait clipboard.writeText(clipboardText)\r\n\r\nawait notify(\"Text transformation applied and copied to clipboard\")\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T02:02:30Z"},{"name":"OCR","description":"","avatar":"https://avatars.githubusercontent.com/u/46276?u=ba13251a7d08a864d70808facf97af2ca722f2b8&v=4","user":"ramiroaraujo","author":"Ramiro Araujo","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1202","url":"https://gist.githubusercontent.com/ramiroaraujo/d1924947b178742c8cd80f320d5e8e63/raw/07d0c86ff19b503bd856dd731294c4866aea7c79/ocr.ts","title":"Screencapture OCR Script","command":"screencapture-ocr-script","content":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/ramiroaraujo/d1924947b178742c8cd80f320d5e8e63/raw/07d0c86ff19b503bd856dd731294c4866aea7c79/ocr.ts\")\r\n\r\nOCR script that uses the OS native screencapture to capture part of your screen, perform OCR on it and copy the text to the clipboard.\r\nNote: I haven't even tested Windows and Linux versions. ChatGPT just wrote those for me :)\r\n\r\n```js\r\n// Name: OCR\r\n// Description: Capture a screenshot and recognize the text using tesseract.js\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\n//both win and linux implementations were created by chatgpt (gpt4), without _any_ tests!! 😅\r\nconst captureScreenshot = async () => {\r\n const tmpFile = `/tmp/screenshot-${Date.now()}.png`;\r\n\r\n if (isMac) {\r\n await exec(`screencapture -i ${tmpFile}`);\r\n } else if (isWin) {\r\n const psScript = `\r\n Add-Type -AssemblyName System.Windows.Forms\r\n [System.Windows.Forms.SendKeys]::SendWait('%{PRTSC}')\r\n Start-Sleep -m 500\r\n $clipboardData = Get-Clipboard -Format Image\r\n $clipboardData.Save('${tmpFile}', [System.Drawing.Imaging.ImageFormat]::Png)\r\n `;\r\n await exec(`powershell -Command \"${psScript.replace(/\\n/g, '')}\"`);\r\n } else if (isLinux) {\r\n // Check if gnome-screenshot is available\r\n try {\r\n await exec('gnome-screenshot --version');\r\n await exec(`gnome-screenshot -f ${tmpFile}`);\r\n } catch (error) {\r\n // If gnome-screenshot is not available, try using ImageMagick's 'import' command\r\n await exec(`import ${tmpFile}`);\r\n }\r\n }\r\n\r\n return tmpFile;\r\n};\r\n\r\nconst recognizeText = async (filePath, language) => {\r\n const { createWorker } = await npm(\"tesseract.js\");\r\n const worker = await createWorker();\r\n\r\n await worker.loadLanguage(language);\r\n await worker.initialize(language);\r\n\r\n const { data } = await worker.recognize(filePath);\r\n\r\n await worker.terminate();\r\n\r\n return data.text;\r\n};\r\n\r\nconst languages = [\r\n { name: \"Spanish\", value: \"spa\" },\r\n { name: \"French\", value: \"fra\" },\r\n { name: \"Portuguese\", value: \"por\" },\r\n { name: \"English\", value: \"eng\" },\r\n];\r\n//@todo train a model for typescript (https://github.com/tesseract-ocr/tesstrain)\r\n\r\n// if ctrl is pressed, show a modal to select a language\r\nconst selectedLanguage = flag.ctrl\r\n ? await arg(\"Select a language:\", languages)\r\n : \"eng\";\r\n\r\n// Hide the Kit modal before capturing the screenshot\r\nawait hide();\r\n\r\nconst filePath = await captureScreenshot();\r\nif (!await pathExists(filePath)) exit()\r\n\r\nconst text = await recognizeText(filePath, selectedLanguage);\r\n\r\nif (text) {\r\n await clipboard.writeText(text.trim());\r\n await notify(\"Text recognized and copied to clipboard\");\r\n} else {\r\n await notify(\"No text found in the screenshot\");\r\n}\r\n\r\n// Clean up temporary file\r\nawait remove(filePath);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-25T01:55:18Z"},{"name":"Toggle Screen Lock","description":"","author":null,"avatar":"https://avatars.githubusercontent.com/u/67016683?u=570ce84e1552b64080e22f8e347275e052928350&v=4","user":"ElTacitos","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1201","url":"","title":"Toggle Screen Lock Macos","command":"toggle-screen-lock-macos","content":"\r\n[Open toggle-screen-lock in Script Kit](https://scriptkit.com/api/new?name=toggle-screen-lock&url=https://gist.githubusercontent.com/ElTacitos/7bb758f516e8e3bc5e1085e306bb0f31/raw/30f26138ddbe17626f1b47c2f2e2c20fa45749b6/toggle-screen-lock.js\")\r\n\r\n```js\r\n// Name: Toggle Screen Lock\r\n// Description: Toggle screen lock on macos (never or 2 minutes)\r\n// Author: ElTacitos\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet password = await arg({\r\n placeholder: \"Enter sudo password\",\r\n secret: true\r\n})\r\n\r\nconst resp = await exec(`echo ${password} | sudo -S pmset -g | grep displaysleep`)\r\nconst currentSleep = resp.stdout.trimStart().trimEnd().replace( /\\s\\s+/g, ' ' ).split(/\\s/)[1]\r\nconst user = (await exec(`whoami`)).stdout\r\n\r\nif (currentSleep === \"0\") {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 2`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 60`)\r\n await notify(\"Enabled screen lock\")\r\n} else {\r\n await exec(`echo ${password} | sudo -S pmset -a displaysleep 0`)\r\n await exec(`defaults write /Users/${user}/Library/Preferences/ByHost/com.apple.screensaver idleTime -int 0`)\r\n await notify(\"Disabled screen lock\")\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-24T21:34:45Z"},{"avatar":"https://avatars.githubusercontent.com/u/23535629?u=0174ab792e2efe02dfeeddacdf991e267e0a79fe&v=4","user":"Jossdz","author":"Jose Carlos Correa","twitter":"JossDz","discussion":"https://github.com/johnlindquist/kit/discussions/1192","url":"","title":"Executing teminal commands on WSL(Windows subsystem for Linux)","command":"executing-teminal-commands-on-wslwindows-subsystem-for-linux","content":"Hey Folks,\r\n\r\nI've been working with Script Kit and encountered concerns regarding my local environment. Specifically, since I'm using Linux within Windows through WSL2 (Windows Subsystem for Linux), I was worried about executing the necessary commands for my workflow.\r\n\r\nUpon contacting John, he suggested using the following environment variable to enable command execution in WSL:\r\n\r\n```env\r\n# The value should be the full path to wsl, this is what is needed on windows 11.\r\nKIT_SHELL=C:\\Windows\\System32\\wsl.exe\r\n```\r\n\r\nAfter implementing this variable, I was able to run my commands in WSL. This is particularly useful for me as I can now start my docker environment with a single command.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-15T23:57:50Z"},{"menu":"Optical Character Recognition","description":"","author":"Kent C. Dodds","twitter":"kentcdodds","avatar":"https://avatars.githubusercontent.com/u/1500684?u=f35a28c4eead6ba9636a8ff5858f6977d8593c43&v=4","user":"kentcdodds","discussion":"https://github.com/johnlindquist/kit/discussions/1190","url":"https://gist.githubusercontent.com/kentcdodds/9d49b047079acb3a2e133f7a55fd1837/raw/a7c04475d41460bc8addfa3132f19244691726f3/ocr.ts","title":"Extract text from images","command":"extract-text-from-images","content":"\r\n[Open ocr in Script Kit](https://scriptkit.com/api/new?name=ocr&url=https://gist.githubusercontent.com/kentcdodds/9d49b047079acb3a2e133f7a55fd1837/raw/a7c04475d41460bc8addfa3132f19244691726f3/ocr.ts\")\r\n\r\n```js\r\n// Menu: Optical Character Recognition\r\n// Description: Extract text from images\r\n// Author: Kent C. Dodds\r\n// Twitter: @kentcdodds\r\n\r\nimport '@johnlindquist/kit'\r\nimport Tesseract from 'tesseract.js'\r\n\r\nconst clipboardImage = await clipboard.readImage()\r\n\r\nif (clipboardImage.byteLength) {\r\n const {data} = await Tesseract.recognize(clipboardImage, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n} else {\r\n let selectedFiles = await getSelectedFile()\r\n let filePaths: Array\r\n\r\n if (selectedFiles) {\r\n filePaths = selectedFiles.split('\\n')\r\n } else {\r\n let droppedFiles = await drop({placeholder: 'Drop images to compress'})\r\n filePaths = droppedFiles.map(file => file.path)\r\n }\r\n for (const filePath of filePaths) {\r\n const {data} = await Tesseract.recognize(filePath, 'eng', {\r\n logger: m => console.log(m),\r\n })\r\n clipboard.writeText(data.text)\r\n }\r\n}\r\n\r\nnotify({\r\n title: 'OCR finished',\r\n message: `Copied text to your clipboard`,\r\n})\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-14T15:23:28Z"},{"name":"BitCoinPrice","description":"","author":"Kostas Minaidis","gitHub":"@kostasx","avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1187","url":"","title":"Get the price of Bitcoin using the BitFinex open API","command":"get-the-price-of-bitcoin-using-the-bitfinex-open-api","content":"```js\r\n// Name: BitCoinPrice\r\n// Description: Get latest Bitcoin price using the Bitfinex open API\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet response = await get(`https://api.bitfinex.com/v1/pubticker/BTCUSD`, {\r\n headers: {\r\n Accept: \"text/plain\",\r\n },\r\n})\r\n\r\nconst data = response.data\r\nawait div(`\r\n
\r\n
Price: $${data.last_price}
\r\n
\r\n`)\r\n```","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-09T20:58:56Z"},{"name":"Open My Links","shortcut":"cmd shift l","author":"Rohit Kumar Saini","gitHub":"@rockingrohit9639","avatar":"https://avatars.githubusercontent.com/u/40729749?u=187731e94f8c8a56eec39f38100360e7581b7f50&v=4","user":"rockingrohit9639","twitter":"_rohit__404","discussion":"https://github.com/johnlindquist/kit/discussions/1183","url":"","title":"Script to open your links in browser","command":"script-to-open-your-links-in-browser","content":"Hey there,\r\nI have created a script which can open your pre-added links in the browser after selecting from the choices.\r\nFor now, it is just a simple script with hardcoded links, in future we can use `db` to store the links for users.\r\n\r\nHere is the script code -\r\n```js\r\n// Name: Open My Links\r\n// Shortcut: cmd shift l\r\n// Author: Rohit Saini\r\n// GitHub: @rockingrohit9639\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst LINKS = {\r\n Github: \"https://github.com/rockingrohit9639\",\r\n LinkedIn: \"https://www.linkedin.com/in/rohit-kumar-saini/\",\r\n} as const;\r\n\r\nconst CHOICES: (keyof typeof LINKS)[] = [\r\n \"Github\",\r\n \"LinkedIn\",\r\n];\r\n\r\nconst linkTitle = await arg(\"Which link to open?\", CHOICES);\r\nconst link = LINKS[linkTitle];\r\nconst command = `open ${link}`;\r\nexec(command);\r\n\r\n```\r\n\r\nHere is the demo of the script - \r\n[link open script.webm](https://user-images.githubusercontent.com/40729749/229710396-04ea1030-354f-4ee3-a08c-c9b2a4d55ca2.webm)\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-04-04T06:47:52Z"},{"name":"force paste","description":"","author":"Trevor Atlas","twitter":"trevoratlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1176","url":"https://gist.githubusercontent.com/trevor-atlas/79a688107d6a3362e23adc58f4cce6ed/raw/5d195be171e8356389bd03ce3c9f55109a3fa7ef/force-paste.ts","title":"Force paste into inputs that don't allow it","command":"force-paste-into-inputs-that-dont-allow-it","content":"\r\n[Open force-paste in Script Kit](https://scriptkit.com/api/new?name=force-paste&url=https://gist.githubusercontent.com/trevor-atlas/79a688107d6a3362e23adc58f4cce6ed/raw/5d195be171e8356389bd03ce3c9f55109a3fa7ef/force-paste.ts\")\r\n\r\n```js\r\n// Name: force paste\r\n// Description: Paste the contents of your clipboard, even in fields that wouldn't let you paste\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n// test it out on the email field here: https://codepen.io/andersschmidt/pen/kOOMmw\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nawait hide();\r\nawait applescript(`tell application \"System Events\" to keystroke the clipboard as text`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-24T20:59:24Z"},{"name":"Paste Clipboard Image as Cloudinary Markdown URL","shortcut":"opt shift v","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1173","url":"","title":"Paste Clipboard Image as Cloudinary Markdown URL","command":"paste-clipboard-image-as-cloudinary-markdown-url","content":"\r\n[Open paste-image-as-url in Script Kit](https://scriptkit.com/api/new?name=paste-image-as-url&url=https://gist.githubusercontent.com/johnlindquist/3593a0bee037b38c23d216191c4e5d7e/raw/b203b5a826d597ffa9e158db06b1ae6757222569/paste-image-as-url.js\")\r\n\r\n```js\r\n// Name: Paste Clipboard Image as Cloudinary Markdown URL\r\n// Shortcut: opt shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet buffer = await clipboard.readImage()\r\n\r\nif (buffer && buffer.length) {\r\n let { default: cloudinary } = await npm(\"cloudinary\")\r\n\r\n cloudinary.config({\r\n cloud_name: await env(\"CLOUDINARY_CLOUD_NAME\"),\r\n api_key: await env(\"CLOUDINARY_API_KEY\"),\r\n api_secret: await env(\"CLOUDINARY_API_SECRET\"),\r\n })\r\n\r\n let response = await new Promise((response, reject) => {\r\n let cloudStream = cloudinary.v2.uploader.upload_stream(\r\n {\r\n folder: \"clipboard\",\r\n },\r\n (error, result) => {\r\n if (error) {\r\n reject(error)\r\n } else {\r\n response(result)\r\n }\r\n }\r\n )\r\n\r\n new Readable({\r\n read() {\r\n this.push(buffer)\r\n this.push(null)\r\n },\r\n }).pipe(cloudStream)\r\n })\r\n\r\n log(response)\r\n\r\n // format however you want\r\n let markdown = `![${response.url}](${response.url})`\r\n await setSelectedText(markdown)\r\n} else {\r\n await div(md(`# No Image in Clipboard`))\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-21T17:26:47Z"},{"name":"FindDuplicate","author":"Kostas Minaidis","gitHub":"@kostasx","supports":"Mac","avatar":"https://avatars.githubusercontent.com/u/1638325?u=a8eb3f1a8fdc490debd2726d4d553474b001bbf9&v=4","user":"kostasx","twitter":"kostas_mns","discussion":"https://github.com/johnlindquist/kit/discussions/1171","url":"","title":"Find Duplicate Files","command":"find-duplicate-files","content":"**Find duplicate files in a folder (first-level only) using MD5 hash:**\r\n\r\n```js\r\n// Name: FindDuplicate\r\n// Author: Kostas Minaidis\r\n// GitHub: @kostasx\r\n// Supports: Mac\r\nimport \"@johnlindquist/kit\"\r\nimport fs from \"fs\"\r\nimport crypto from \"crypto\"\r\n\r\nconst folder = await drop();\r\nconst dir = await readdir(folder[0].path)\r\nlet content = `\r\n| Filename | MD5 Hash |\r\n| -------- | -------- |\r\n`;\r\nconst hashes = {}\r\ndir.forEach(file => {\r\n const fullPath = `${folder[0].path}/${file}`\r\n\r\n const stats = fs.statSync(fullPath);\r\n if (stats.isDirectory()) { return; }\r\n\r\n const fileData = fs.readFileSync(fullPath)\r\n const hash = crypto.createHash('md5').update(fileData).digest('hex')\r\n if (hashes[hash]) {\r\n return hashes[hash].push(file)\r\n } \r\n hashes[hash] = [file]\r\n})\r\n\r\nObject.entries(hashes).forEach(([hash, listOfFiles]) => {\r\n if (listOfFiles.length > 1) {\r\n listOfFiles.forEach(file => {\r\n content += `| ${file} | ${hash.slice(0,4) + \"...\" + hash.slice(-4)} |\\n`\r\n })\r\n }\r\n})\r\n\r\nawait div(md(content))\r\n\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-18T21:42:54Z"},{"menu":"Icebreaker","description":"","author":"Trevor Atlas","twitter":"trevoratlas","avatar":"https://avatars.githubusercontent.com/u/5009188?u=6d1d4e4744b6cc869b7a86d8831748b058c681b3&v=4","user":"trevor-atlas","discussion":"https://github.com/johnlindquist/kit/discussions/1169","url":"https://gist.githubusercontent.com/trevor-atlas/5eea582ea68faf7a4aa68d1f6ee487bd/raw/9acdba7e0276a9aef96f608dde594f445897e013/icebreaker.ts","title":"Get a random icebreaker question","command":"get-a-random-icebreaker-question","content":"\r\n[Open icebreaker in Script Kit](https://scriptkit.com/api/new?name=icebreaker&url=https://gist.githubusercontent.com/trevor-atlas/5eea582ea68faf7a4aa68d1f6ee487bd/raw/9acdba7e0276a9aef96f608dde594f445897e013/icebreaker.ts\")\r\n\r\n```js\r\n// Menu: Icebreaker\r\n// Description: Get a random icebreaker question\r\n// Author: Trevor Atlas\r\n// Twitter: @trevoratlas\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst dbvalues = await db('icebreakers');\r\nconst icebreakers: string[] = dbvalues.data;\r\n\r\nconst getRandomElement = (arr: T[]) => {\r\n const index = Math.floor(Math.random() * arr.length);\r\n return arr[index];\r\n};\r\n\r\nconst item = getRandomElement(icebreakers);\r\n\r\nawait div(\r\n `\r\n
\r\n
${item}
\r\n
\r\n `\r\n);\r\n\r\n```\r\n\r\n\r\nAdd a `json` array of icebreaker questions in the `kenv` `db` folder called `icebreakers.json`\r\nFor example\r\n```json\r\n[\r\n \"Show us the weirdest thing you have in the room with you right now.\",\r\n \"There is a free, round-trip shuttle to Mars. The catch: it will take one year of your life to go, visit, and come back. Are you in?\",\r\n \"What is your least favorite thing about technology?\",\r\n \"What superpower would you most want?\",\r\n \"What food is best with cheese?\",\r\n \"Would you go in the mother-ship with aliens if they landed on Earth tomorrow?\",\r\n \"Would you join a community in space if it was permanent?\",\r\n \"Would you rather live 100 years in the past or 100 years in the future?\",\r\n \"You are the best criminal mastermind in the world. What crime would you commit if you knew you would get away with it?\",\r\n \"You can only eat one food again for the rest of your life. What is it?\",\r\n \"You can visit any fictional time or place. Which would you pick?\",\r\n \"In your time as a student in K-12, what made an impact on you. Not who, but what? What do you remember that influenced you today?\",\r\n \"How would you hide a giraffe from the government?\",\r\n \"If you were an inanimate object, what would you be and why?\",\r\n \"What is the most trivial thing about which you have a strong opinion?\",\r\n \"What is the smallest thing for which you are grateful?\",\r\n \"If you could change one thing about yourself physically, what would you change?\",\r\n \"What single event or decision do you think most affected the rest of your life?\",\r\n \"What do you fear, despite having no real reason to do so? Basically, what is an irrational fear you have?\",\r\n \"Do you have any conspiracy theories? If so, what are they?\",\r\n \"What scientific or technological advance blows your mind? Is there any technology that seems so futuristic and advanced you're surprised it actually exists?\",\r\n \"What is something you don't realise is weird until you really think about it?\",\r\n \"You can transport one furious elephant into any point in history, where would you put it?\",\r\n \"If you could make one thing that is now legal, illegal, and one thing that is illegal, legal, what laws would change?\",\r\n \"Would you agree to go without showering, brushing your teeth, and using deodorant for six months to win $500,000? You are not allowed to talk about the deal with anyone until the six months end, or the offer is gone.\",\r\n \"What's the best trip (traveling wise) you ever had?\",\r\n \"Does pineapple go on pizza?\",\r\n \"If you could live anywhere in the world for a year, where would it be?\",\r\n \"What's your favorite seat on an airplane?\",\r\n \"What is your spirit animal? (The animal who is most similar to your personality.)\",\r\n \"What is your favorite thing to do by yourself?\",\r\n \"Have you ever experienced a natural disaster like a hurricane or tornado?\",\r\n \"If you had to delete all but 3 apps from your smartphone, which ones would you keep? (Three apps that have changed your life.)\",\r\n \"If you had to choose between only having a cell phone or a car for the rest of your life, which would you choose?\",\r\n \"What is your favorite tv series?\",\r\n \"What is your favorite book?\",\r\n \"How would you change your life today if the average life expectancy was 400 years?\",\r\n \"A genie grants you three wishes but none of them can directly benefit you. What would those wishes be?\",\r\n \"What is your favorite smell and why?\",\r\n \"According to you, what is the most mind-numbingly dull movie ever made?\",\r\n \"If given the choice of having a talk show host narrate your life, who would you choose?\",\r\n \"Which reality TV show is your guilty pleasure?\",\r\n \"All in all, the movie that had the most significant impact on your life and why?\",\r\n \"If you could switch your life with any fictional character, who would it be?\",\r\n \"Decidedly, you must choose a fictional world that'll become the new reality. Which one would you pick?\",\r\n \"According to you, what is the most monotonous sport to watch?\",\r\n \"Who would be the first celebrity guest in your very own talk show?\",\r\n \"Without a doubt, who is the greatest actor that has ever graced the world?\",\r\n \"If you had the chance to be in the Olympics, which sport would you compete in?\",\r\n \"Generally, which real life person are you most inspired by?\",\r\n \"What's the most underrated actor that you know of?\",\r\n \"What is your 'I wish I had started doing this earlier in my life'?\",\r\n \"What is the coolest website you've ever visited?\",\r\n \"What is your favorite polite insult?\"\r\n]\r\n```\r\n\r\nUse the script!\r\n\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-17T20:23:41Z"},{"name":"Screenshot URL","avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1164","url":"https://gist.githubusercontent.com/SimplGy/0e89bc0a60548b32cac9c0db806d9cd4/raw/149f2b5018177cc777bed207b9dc89f664fe54d8/screenshot-url.ts","title":"A script that asks you to enter a url. it opens it, and takes a small screenshot of it.","command":"a-script-that-asks-you-to-enter-a-url-it-opens-it-and-takes-a-small-screenshot-of-it","content":"\r\n[Open screenshot-url in Script Kit](https://scriptkit.com/api/new?name=screenshot-url&url=https://gist.githubusercontent.com/SimplGy/0e89bc0a60548b32cac9c0db806d9cd4/raw/149f2b5018177cc777bed207b9dc89f664fe54d8/screenshot-url.ts\")\r\n\r\n```js\r\n// Name: Screenshot URL\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\n// get URL from user\r\nlet urlFromUser = await arg(\"Enter the URL to screenshot\");\r\nif (!urlFromUser.match(/^https?:\\/\\//)) {\r\n urlFromUser = `http://${urlFromUser}`;\r\n}\r\nconst pathObj = path.parse(urlFromUser);\r\nlog(pathObj);\r\n\r\n// config\r\nlet timeout = 5_000;\r\nconst FOLDER = 'Downloads/screenshot-url';\r\nconst screenshotFolder = home(FOLDER);\r\nconst filename = `${pathObj.name}${pathObj.ext}.png`\r\nconst screenshotPath = home(FOLDER, filename);\r\n\r\n// Open the window\r\nconst browser = await chromium.launch({ timeout, headless: false });\r\nconst context = await browser.newContext({ colorScheme: \"dark\" });\r\nconst page = await context.newPage();\r\nawait page.setViewportSize({\r\n width: 800,\r\n height: 600,\r\n});\r\npage.setDefaultTimeout(timeout);\r\n\r\ntry {\r\n // docs: https://playwright.dev/docs/api/class-page\r\n await page.goto(urlFromUser);\r\n await page.screenshot({ path: screenshotPath })\r\n \r\n // TODO: shrink the file to a thumbnail\r\n\r\n await revealFile(screenshotFolder)\r\n log(`Done`)\r\n\r\n} catch (error) {\r\n warn('error', error);\r\n}\r\n\r\nawait browser.close();\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-12T01:24:50Z"},{"name":"Natural Language Shell Command","description":"","author":"Laura Okamoto","twitter":null,"avatar":"https://avatars.githubusercontent.com/u/127610787?u=b01a40872f605669b97bb3f31c0a622988ccd019&v=4","user":"laura-ok","discussion":"https://github.com/johnlindquist/kit/discussions/1163","url":"","title":"natural language shell command","command":"natural-language-shell-command","content":"\r\n[Open natural-language-shell-command in Script Kit](https://scriptkit.com/api/new?name=natural-language-shell-command&url=https://gist.githubusercontent.com/laura-ok/f2cc8d4cfb1211ffc7a494e8f89fff80/raw/04e765fc3b70d0705e874ed62ee16125798394b0/natural-language-shell-command.js\")\r\n\r\n```js\r\n// Name: Natural Language Shell Command\r\n// Description: Convert a natural language command to a shell command\r\n// Author: Laura Okamoto\r\n// Twitter: @laura_okamoto\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst { Configuration, OpenAIApi } = await npm(\"openai\");\r\n\r\nconst configuration = new Configuration({\r\n apiKey: await env(\"OPENAI_API_KEY\"),\r\n});\r\nconst openAI = new OpenAIApi(configuration);\r\n\r\nconst res = await arg(\"Describe the shell command you want to run\");\r\nconst prompt = `Use the following shell command to \"${res}\":`;\r\nconst completion = await openAI.createCompletion({\r\n model: \"text-davinci-003\",\r\n prompt,\r\n temperature: 0,\r\n max_tokens: 4069,\r\n});\r\n\r\nsetSelectedText(completion.data.choices[0].text.trim());\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-11T15:02:43Z"},{"name":"Preview CSS Color","description":"","author":"Josh Davenport-Smith","twitter":"joshdprts","avatar":"https://avatars.githubusercontent.com/u/757828?u=789f3b1408f5c32253eab54c5d8e5d14d2b27179&v=4","user":"joshdavenport","discussion":"https://github.com/johnlindquist/kit/discussions/1162","url":"https://gist.githubusercontent.com/joshdavenport/86cb857671226ea6fb530c6bd7923bdf/raw/8ffbbe2c3ca55d72a54233d39c88095d338adade/preview-css-color.ts","title":"Preview CSS Color","command":"preview-css-color","content":"![image](https://user-images.githubusercontent.com/757828/224484149-4376fcf8-bce5-4adb-a2e0-bb0e9389a42a.png)\r\n\r\n[Open preview-css-color in Script Kit](https://scriptkit.com/api/new?name=preview-css-color&url=https://gist.githubusercontent.com/joshdavenport/86cb857671226ea6fb530c6bd7923bdf/raw/8ffbbe2c3ca55d72a54233d39c88095d338adade/preview-css-color.ts\")\r\n\r\n```js\r\n// Name: Preview CSS Color\r\n// Description: Preview any CSS color accepted by background-color\r\n// Author: Josh Davenport-Smith\r\n// Twitter: @joshdprts\r\n\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst color = await arg(\"Color\");\r\n\r\nawait div(`\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
${color}
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n \r\n`);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-11T12:24:19Z"},{"name":"Units Convert","description":"","author":"Vedinsoh","gitHub":"@Vedinsoh","avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1160","url":"","title":"Units Convert","command":"units-convert","content":"\r\n[Open units-convert in Script Kit](https://scriptkit.com/api/new?name=units-convert&url=https://gist.githubusercontent.com/Vedinsoh/40e74c82f688a80849da32afde7a5130/raw/2004a082fc3a3142de9ae8510fd42569713ae50e/units-convert.js\")\r\n\r\n```js\r\n// Name: Units Convert\r\n// Description: Convert between metric and imperial units\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nconst convert = await npm(\"convert-units\");\r\n\r\nconst getAllPossibilities = (unit) => {\r\n const possibilities = unit\r\n ? convert().from(unit).possibilities()\r\n : convert().possibilities();\r\n\r\n return possibilities\r\n .map((u) => {\r\n const uDetails = convert().describe(u);\r\n return {\r\n name: `${u} - ${uDetails.plural}`,\r\n value: u,\r\n };\r\n })\r\n .sort((a, b) => {\r\n const aDetails = convert().describe(a.value);\r\n const bDetails = convert().describe(b.value);\r\n if (aDetails.system === bDetails.system) {\r\n return aDetails.value - bDetails.value;\r\n }\r\n return aDetails.system - bDetails.system;\r\n });\r\n};\r\n\r\nconst getUnitString = (unit) => {\r\n const unitDetails = convert().describe(unit);\r\n return `${unitDetails.plural} (${unit})`;\r\n};\r\n\r\nconst convertUnits = (from, to, amount) => {\r\n return String(convert(amount).from(from).to(to));\r\n};\r\n\r\nconst fromUnit = await arg({\r\n placeholder: \"From\",\r\n choices: getAllPossibilities(),\r\n enter: \"To\",\r\n});\r\n\r\nconst toUnit = await arg({\r\n placeholder: \"To\",\r\n choices: getAllPossibilities(fromUnit),\r\n enter: \"Amount\",\r\n hint: `Convert from ${fromUnit} to...`,\r\n});\r\n\r\nawait arg({\r\n placeholder: \"Amount\",\r\n type: \"number\",\r\n enter: \"Exit\",\r\n hint: `${getUnitString(fromUnit)} equals...`,\r\n onInput: (input) => {\r\n const result = convertUnits(fromUnit, toUnit, input);\r\n setPanel(md(`# ${result} ${getUnitString(toUnit)}`));\r\n },\r\n shortcuts: [\r\n {\r\n name: \"Copy result\",\r\n key: `${cmd}+c`,\r\n onPress: (input) => {\r\n copy(convertUnits(fromUnit, toUnit, input));\r\n },\r\n bar: \"right\",\r\n },\r\n ],\r\n});\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-07T18:09:07Z"},{"name":"IP & Domain Lookup","description":"","author":"Vedinsoh","gitHub":"@Vedinsoh","avatar":"https://avatars.githubusercontent.com/u/16418183?u=a5f010c548d309527307cc5817b5d816eba639b3&v=4","user":"Vedinsoh","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1158","url":"","title":"IP & Domain Lookup","command":"ip-and-domain-lookup","content":"\r\n[Open ip-lookup in Script Kit](https://scriptkit.com/api/new?name=ip-lookup&url=https://gist.githubusercontent.com/Vedinsoh/140a888222f85f8a8da1e65fdbdd87bb/raw/9ad2f7dedacc718ab906fcad75fc43c9c3b05451/ip-lookup.js\")\r\n\r\n```js\r\n// Name: IP & Domain Lookup\r\n// Description: Get information about an IP address or domain\r\n// Author: Vedinsoh\r\n// GitHub: @Vedinsoh\r\n\r\nimport \"@johnlindquist/kit\";\r\nimport net from \"node:net\";\r\nimport { URL } from \"node:url\";\r\n\r\nconst getLookupData = async (query) => {\r\n // Reference: https://ip-api.com/docs/api:json\r\n const response = await get(\r\n `http://ip-api.com/json/${query}?fields=status,message,continent,country,countryCode,regionName,city,zip,lat,lon,timezone,isp,org,as,query`\r\n );\r\n\r\n if (response.data.status === \"fail\") {\r\n throw new Error(response.data.message);\r\n }\r\n\r\n return response.data;\r\n};\r\n\r\nlet lookupQuery = await arg({\r\n placeholder: \"Enter IP address or domain\",\r\n validate: (value) => {\r\n if (net.isIP(value) !== 0) {\r\n return true;\r\n } else {\r\n try {\r\n new URL(`https://${value}`);\r\n return true;\r\n } catch (e) {\r\n return \"Please enter a valid IP address or domain\";\r\n }\r\n }\r\n },\r\n});\r\n\r\nconst data = await getLookupData(lookupQuery);\r\n\r\ndiv(\r\n md(`\r\n# IP Lookup: ${lookupQuery}\r\n\r\n- **IP:** ${data.query}\r\n- **ISP:** ${data.isp}\r\n- **Organization:** ${data.org}\r\n- **AS:** ${data.as}\r\n- **Continent:** ${data.continent}\r\n- **Country:** ${data.country} (${data.countryCode})\r\n- **Region:** ${data.regionName}\r\n- **City:** ${data.city}\r\n- **Zip Code:** ${data.zip}\r\n- **Latitude:** ${data.lat}\r\n- **Longitude:** ${data.lon}\r\n- **Timezone:** ${data.timezone}\r\n`)\r\n);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-07T15:26:33Z"},{"avatar":"https://avatars.githubusercontent.com/u/4293840?v=4","user":"fischgeek","author":null,"twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1154","url":"","title":"Simple Snippet Creator","command":"simple-snippet-creator","content":"I wanted a quick way to make very simple text replacements. \r\n\r\n```\r\nlet [txt, rep] = await fields([\"Text\", \"Replacement\"])\r\nlet dir = \"/Users/fischgeek/.kenv/scripts\" // <- Update to your specific path\r\nawait writeFile(`${dir}/${txt}.js`, `//Snippet: ${txt}\\nawait keyboard.type(\"${rep}\")`)\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-05T17:30:25Z"},{"name":"Raindrop","description":"","author":"Bruno Paz","github":"@brpaz","avatar":"https://avatars.githubusercontent.com/u/184563?u=27a3324127667282e6eb95d538d5b58b4d007352&v=4","user":"brpaz","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1150","url":"","title":"Raindrop Bookmarks search","command":"raindrop-bookmarks-search","content":"Open your [Raindrop](app.raindrop.io/) bookmarks from ScriptKit\r\n\r\n```ts\r\n// Name: Raindrop\r\n// Description: Search your Raindrop.io bookmarks\r\n// Author: Bruno Paz\r\n// Github: @brpaz\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { Choice } from \"@johnlindquist/kit\"\r\n\r\nconst COLLECTION_ID_ALL = 0\r\nconst COLLECTION_ID_UNSORTED = -1\r\n\r\ninterface RaindropResponse {\r\n items: RaindropBookmark[]\r\n}\r\n\r\ninterface RaindropBookmark {\r\n _id: string\r\n title: string\r\n link: string\r\n excerpt: string\r\n tags: string[]\r\n created: string\r\n type: string\r\n}\r\n\r\nconst raindropAPIKey = await env(\"RAINDROP_API_KEY\", {\r\n placeholder: \"Enter your Raindrop.io Test API Key\",\r\n hint: md(\r\n `Get a [Raindrop.io Test API Key](https://app.raindrop.io/settings/integrations)`\r\n ),\r\n\r\n secret: true,\r\n})\r\n\r\nasync function raindropSearch(query: string, collectionId: number): Promise {\r\n const url = `https://api.raindrop.io/rest/v1/raindrops/${collectionId}?search=${query}&access_token=${raindropAPIKey}`\r\n\r\n const response = await get(url)\r\n const data: RaindropResponse = await response.data\r\n\r\n return data.items.map((item) => ({\r\n name: item.title,\r\n description: item.excerpt,\r\n value: item.link,\r\n onSubmit: async () => {\r\n open(item.link)\r\n }\r\n }))\r\n}\r\n\r\nasync function allBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_ALL)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nasync function unsortedBookmarks() {\r\n await arg({\r\n placeholder: \"Search your Raindrop.io unsorted bookmarks\",\r\n enter: \"Open in Raindrop.io\",\r\n onInput: _.debounce(async (query) => {\r\n setHint(\"Searching...\")\r\n setChoices([])\r\n\r\n if (!query) {\r\n return;\r\n }\r\n\r\n try {\r\n const choices = await raindropSearch(query, COLLECTION_ID_UNSORTED)\r\n setChoices(choices)\r\n setHint(choices.length === 0 ? \"No bookmarks found\" : \"\")\r\n } catch (error) {\r\n setHint(`Failed to fetch bookmarks: ${error}`)\r\n }\r\n }, 200),\r\n });\r\n}\r\n\r\nonTab(\"Unsorted\", unsortedBookmarks);\r\nonTab(\"All\", allBookmarks);\r\n``` ","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-03-04T19:07:56Z"},{"name":"Escape Backticks","avatar":"https://avatars.githubusercontent.com/u/6349395?u=650885c8ac85a967ab8787bfffad65e1206266d1&v=4","user":"abisuq","author":"__","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1142","url":"","title":"Escape Backticks for copy paste string in javascript","command":"escape-backticks-for-copy-paste-string-in-javascript","content":" [Open browse-scriptkit in Script Kit](https://scriptkit.com/api/new?name=browse-scriptkit&url=https://gist.githubusercontent.com/abisuq/a27c130faa4ceb2b582a78b929bde0b2/raw/5d480d52863532d61adbb363fcb217b3f92c2cb8/browse-scriptkit.js\")\r\n\r\n```js\r\n// Name: Escape Backticks\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"Paste text to escape backticks\");\r\nawait copy(text.replace(/`/g, '\\\\`'));\r\n\r\n\r\n\r\n```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-24T05:33:20Z"},{"menu":"Center App","description":"","author":"Alois Carrera","avatar":"https://avatars.githubusercontent.com/u/60891163?u=b1985aac6c4755041fd849428360e69819e2339c&v=4","user":"AloisCRR","twitter":"AloisCRR","discussion":"https://github.com/johnlindquist/kit/discussions/1141","url":"","title":"Center focused app based on window dimensions","command":"center-focused-app-based-on-window-dimensions","content":"\r\n[Open center-app in Script Kit](https://scriptkit.com/api/new?name=center-app&url=https://gist.githubusercontent.com/AloisCRR/4a27c9e04b145be6a32e6ac4fa07894c/raw/f01bffb81564899dc01d7c931d82e072ec1918ba/center-app.js\")\r\n\r\n```js\r\n// Menu: Center App\r\n// Description: Center current focused app based on window size\r\n// Author: Alois Carrera\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst activeScreen = await getActiveScreen()\r\n\r\nconst {\r\n workArea: {\r\n height,\r\n width,\r\n x: workAreaX,\r\n y: workAreaY\r\n }\r\n} = activeScreen\r\n\r\nconst activeAppBounds = await getActiveAppBounds()\r\n\r\nconst { top, left, right, bottom } = activeAppBounds\r\n\r\nconst windowHeight = bottom - top\r\n\r\nconst windowYCenter = windowHeight / 2\r\n\r\nconst windowWidth = right - left\r\n\r\nconst windowXCenter = windowWidth / 2\r\n\r\nsetActiveAppPosition({\r\n x: workAreaX + (width / 2) - windowXCenter,\r\n y: workAreaY + (height / 2) - windowYCenter\r\n})\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-23T13:54:56Z"},{"name":"daily note","description":"","avatar":"https://avatars.githubusercontent.com/u/830800?v=4","user":"johtso","author":"Johannes","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1126","url":"https://gist.githubusercontent.com/johtso/b6a5d6e85d0805dbd25d5a36ffda6abb/raw/ef858f9129001cf187d0eb718108f7d734e2cef6/daily-note.ts","title":"Open daily note in Obsidian","command":"open-daily-note-in-obsidian","content":"\r\n[Open daily-note in Script Kit](https://scriptkit.com/api/new?name=daily-note&url=https://gist.githubusercontent.com/johtso/b6a5d6e85d0805dbd25d5a36ffda6abb/raw/ef858f9129001cf187d0eb718108f7d734e2cef6/daily-note.ts\")\r\n\r\n```js\r\n// Name: daily note\r\n// Description: Open today's daily note in obsidian\r\n\r\n// You must install the Actions URI plugin and have the daily notes plugin enabled\r\n// Currently MacOS only\r\n\r\nimport \"@johnlindquist/kit\"\r\nimport { homedir } from \"os\"\r\nimport { join as joinPath } from \"path\"\r\n\r\nconst VAULT_NAME = await env(\r\n \"VAULT_NAME\",\r\n async () => {\r\n const vaultNames = await getVaultNames().catch(() => [])\r\n if (vaultNames.length === 1) {\r\n return vaultNames[0]\r\n } else {\r\n return await arg(\r\n \"Which vault do you want to use?\",\r\n vaultNames\r\n )\r\n }\r\n }\r\n);\r\n\r\nconst CREATE_URI = `obsidian://actions-uri/daily-note/create?vault=${VAULT_NAME}&silent=true`\r\nconst OPEN_URI = `obsidian://actions-uri/daily-note/open-current?vault=${VAULT_NAME}`\r\n\r\nawait applescript(`\r\n tell application \"Obsidian\"\r\n open location \"${CREATE_URI}\"\r\n open location \"${OPEN_URI}\"\r\n activate\r\n end tell\r\n`);\r\n\r\nasync function getVaultNames() {\r\n const obsidianConfPath = joinPath(homedir(), \"Library/Application Support/obsidian/obsidian.json\")\r\n\r\n // {\"vaults\":{\"9aeaa3aaa2ad0602\":{\"path\":\"/Users/human/Documents/Obsidian Vault\",\"ts\":1651412412801,\"open\":true}}}\r\n const obsidianConf = JSON.parse(await (await readFile(obsidianConfPath)).toString())\r\n const vaults = obsidianConf.vaults\r\n const vaultNames = Object.keys(vaults).map((vaultId) => vaults[vaultId].path.split(\"/\").pop())\r\n return vaultNames\r\n}\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T23:20:40Z"},{"menu":"Search Anime","description":"","author":"Ambushfall","tODO":"When on click starts working change state to the next result","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1122","url":"","title":"Anime Search - Working - Updated","command":"anime-search-working-updated","content":"\r\n[Open anime-search in Script Kit](https://scriptkit.com/api/new?name=anime-search&url=https://gist.githubusercontent.com/Ambushfall/c27b170000791f197fcfa5ca154a966b/raw/8bd65a0da0841f83ba892aaa4e3e72649f4283ee/anime-search.js\")\r\n\r\nUpdated Johns amazing script to work with the new v4 Api, and using widget instead of the deprecated showImage method.\r\n\r\nProps to John for making all of this possible!\r\n\r\n[Original script](https://www.scriptkit.com/johnlindquist/anime-search)\r\n\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Menu: Search Anime\r\n// Description: Use the jikan.moe API to search anime\r\n// Author: John Lindquist, Updated by Ambushfall\r\n\r\nlet anime = await arg(\"Anime:\")\r\n\r\nlet response = await get(\r\n `https://api.jikan.moe/v4/anime?q=${anime}`\r\n)\r\n\r\nlet { images, title } = response.data.data[0]\r\n\r\nlet { jpg } = images\r\n\r\nlet { image_url, small_image_url, large_image_url } = jpg\r\n\r\nconst html = `\r\n\r\n
\r\n`;\r\n\r\nlet wg = await widget(html, {\r\n state: {\r\n url: large_image_url\r\n }\r\n})\r\n\r\nwg.onResized(async () => {\r\n wg.fit()\r\n})\r\n\r\n// win32 on-click not working so this does nothing really.\r\n\r\n// TODO: When on click starts working change state to the next result\r\n// wg.onClick((event) => event.targetId === \"x\" ? wg.close() : inspect(event.targetId));\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T01:47:54Z"},{"menu":"Clipboard History","description":"","shortcut":"command shift v","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1121","url":"","title":"Part 2: Clipboard special history, preview images as well","command":"part-2-clipboard-special-history-preview-images-as-well","content":"\r\n[Open clipboard-history in Script Kit](https://scriptkit.com/api/new?name=clipboard-history&url=https://gist.githubusercontent.com/Ambushfall/444bb14ca4b5268ea855cec8431b7dfc/raw/3fb4c0962b3cfdf2220fd96f71190e4ec1e872e5/clipboard-history.js\")\r\n\r\n```js\r\n// Menu: Clipboard History\r\n// Description: Copy something from the clipboard history\r\n// Shortcut: command shift v\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { history } = await db(\"clipboard-history\")\r\n\r\nlet { value, type } = await arg(\"What to paste?\", () => {\r\n return history.map(({ value, type, timestamp, secret }) => {\r\n return {\r\n type,\r\n name: secret ? value.slice(0, 4).padEnd(10, \"*\") : value,\r\n value: {\r\n value,\r\n type,\r\n },\r\n description: timestamp,\r\n preview:\r\n type === \"image\"\r\n ? md(`![timestamp](${value})`)\r\n : value.includes(\"\\n\")\r\n ? `
${value\r\n .split(\"\\n\")\r\n .map((line) => `
${line}
`)\r\n .join(\"\")}
`\r\n : null,\r\n }\r\n })\r\n})\r\n\r\nif (type === \"image\") {\r\n await copyPathAsImage(value)\r\n await keystroke(\"command v\")\r\n}\r\n\r\nif (type === \"text\") {\r\n await setSelectedText(value)\r\n}\r\n\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T00:17:08Z"},{"menu":"Copy to Clipboard","description":"","shortcut":"command shift c","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1120","url":"","title":"Clipboard special history, preview images as well","command":"clipboard-special-history-preview-images-as-well","content":"\r\n\r\n\r\n[Open copy-to-clipboard in Script Kit](https://scriptkit.com/api/new?name=copy-to-clipboard&url=https://gist.githubusercontent.com/Ambushfall/db9f545a9099a0a674f14679c862c0c3/raw/f9712e16d1965f16c578ffe4ad0ccb2c1e493086/copy-to-clipboard.js\")\r\n\r\n\r\ncopy-to-clipboard.js\r\n```js\r\n// Menu: Copy to Clipboard\r\n// Description: Save to Clipboard history\r\n// Shortcut: command shift c\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet { write, history } = await db(\"clipboard-history\", { history: [{ value: \"\", type: \"\", timestamp: \"\", secret: \"\" }] })\r\n\r\nconst clipboardVal = await clipboard.readText();\r\n\r\n\r\nconst newValue = {\r\n value: clipboardVal,\r\n timestamp: new Date(Date.now()).toLocaleString('en-GB', { timeZone: 'UTC' }),\r\n secret: clipboardVal.includes('secret'),\r\n type: /(http)?s?:?(\\/\\/[^\"']*\\.(?:png|jpg|jpeg|gif|png|svg))/i.test(clipboardVal) ? \"image\" : \"text\"\r\n}\r\n\r\nhistory.push(newValue)\r\n\r\nawait write()\r\n```\r\n\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-13T00:12:47Z"},{"name":"today-timestamp","description":"","avatar":"https://avatars.githubusercontent.com/u/548809?u=20af8ca20056a2a9e4bdb26c863b7a799dbd1c55&v=4","user":"SimplGy","author":"Eric Miller","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1117","url":"https://gist.githubusercontent.com/SimplGy/2676c4ccad30d9c71f7096422eb5fd44/raw/0da283bff42524450ee9f162c24451e1a8332a47/today-timestamp.ts","title":"Today's date as ISO","command":"todays-date-as-iso","content":"Completely ripped from https://github.com/johnlindquist/kit/discussions/1116 (Thanks Daniel!) I just like the ISO format better.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/SimplGy/2676c4ccad30d9c71f7096422eb5fd44/raw/0da283bff42524450ee9f162c24451e1a8332a47/today-timestamp.ts\")\r\n\r\n```js\r\n// Name: today-timestamp\r\n// Description: inserts today's date in \"ISO\" format 2023-02-11\r\n// Snippet:\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\nconst formatted = `${today.getFullYear()}-${twoDigits(today.getMonth() + 1)}-${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-11T20:44:58Z"},{"name":"today-timestamp","description":"","snippet":"!tday","avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1116","url":"https://gist.githubusercontent.com/danielo515/9356f1af0b642dd23f6cdc188d73d7be/raw/6f8ca2cf7a666762c533bb47773403f210c55f49/today-timestamp.ts","title":"Insert current timestamp as YYYY/MM/DD","command":"insert-current-timestamp-as-yyyymmdd","content":"This snippet is basic, and stupid, but you will be happy to have it around when you need it.\r\nRather than manually input the current today date, you just type `!tday` and you get it.\r\n\r\n[Open today-timestamp in Script Kit](https://scriptkit.com/api/new?name=today-timestamp&url=https://gist.githubusercontent.com/danielo515/9356f1af0b642dd23f6cdc188d73d7be/raw/6f8ca2cf7a666762c533bb47773403f210c55f49/today-timestamp.ts\")\r\n\r\n```ts\r\n// Name: today-timestamp\r\n// Description: inserts the today date (not including time) formatted as YYYY/MM/DD\r\n// Snippet: !tday\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nfunction twoDigits(number: number): string {\r\n\treturn number.toString().padStart(2, '0');\r\n}\r\n\r\nawait hide();\r\nconst today = new Date();\r\n// Format date to YYYY/MM/DD format\r\nconst formatted = `${today.getFullYear()}/${twoDigits(today.getMonth() + 1 )}/${twoDigits(today.getDate())}`;\r\nawait keyboard.type(formatted);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-11T06:58:35Z"},{"name":"url encode","avatar":"https://avatars.githubusercontent.com/u/2270425?u=85a40c344f9f2b55652cede930cc8e1f8eaf32ad&v=4","user":"danielo515","author":"Daniel Rodríguez Rivero","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1111","url":"https://gist.githubusercontent.com/danielo515/b2b2576155111a3a8fb73b47da2efac8/raw/cf81906c75996c52064151670cad363a71c7317d/url-encode.ts","title":"Ask for user input and transform it to a url encoded string","command":"ask-for-user-input-and-transform-it-to-a-url-encoded-string","content":"Many times I don't want to url encode what I have on the clipboard, but I want to manually type it. This little script is for that.\r\n\r\n[Open url-encode in Script Kit](https://scriptkit.com/api/new?name=url-encode&url=https://gist.githubusercontent.com/danielo515/b2b2576155111a3a8fb73b47da2efac8/raw/cf81906c75996c52064151670cad363a71c7317d/url-encode.ts\")\r\n\r\n```js\r\n// Name: url encode\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst text = await arg(\"What do you want to encode\");\r\nconst encoded = encodeURIComponent(text)\r\nawait copy(encoded);\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-09T15:41:54Z"},{"shortcut":"cmd opt f","avatar":"https://avatars.githubusercontent.com/u/99090177?u=23294c71201154e9389c53cc961811acb9aff635&v=4","user":"wisskirchenj","author":"Jürgen Wißkirchen","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1110","url":"","title":"Full text search in (java-)files over all my projects && open hit(s) in IDE","command":"full-text-search-in-java-files-over-all-my-projects-andand-open-hits-in-ide","content":"My typical usecase: I remember that I used some class (e.g. Mockitos InOrde, CsvSource or ExecutorService) in a similar situation or test and start searching in which project that was. My root contains 30+ projects - so sometimes this took me 15+ min or I had to google again, howto use `find -exec` - and even then it's clumsy.. \r\n\r\nNow this is perfect to me: I enter the search string (class, method, ...) and the surrounding context width in hits (the 'n' in grep -n).\r\nThen a div-container shows me all scrollable hits and I can refresh my mind on what I did years ago. \r\nFinally I hit Return and get a Button-List widget with all filenames with hits, where i can click on. This opens the project in IDEA, opens the file with the hit inside this project and even puts the clipboard copied search string by keystroke into IDEA's search dialog, so I can navigate with arrows...\r\nSuper helpful to me. :smile:\r\n\r\nTo reuse, there is a little customization needed, as I hardcoded my projects root and file pattern *.java. \r\nAlso the exclusion of 'z'-starting directories is sure special to me - but all that should be easy to adapt. \r\nAlso, I am more then happy to help, if there's need.\r\n**Note:** Keystrokes in IDEA at end of script, needs accessibility rights. My platform is MacOS.\r\n\r\n```ts\r\n// Shortcut: cmd opt f\r\nimport \"@johnlindquist/kit\";\r\n\r\nconst smallArg = (placeholder: string) => arg({\r\n placeholder: placeholder,\r\n height: 100,\r\n width: 500\r\n});\r\n\r\nconst substring = await smallArg(\"Substring to search:\");\r\nconst lines = await smallArg(\"# surrounding lines in results:\");\r\n\r\nconst PROJECT_ROOT = \"/Users/jwisskirchen/IdeaProjects\";\r\nconst CLOSE_LABEL = \"Close\";\r\n\r\n// execute find java-files on all project-subdirs not starting with 'z' (as those are special ones...)\r\nconst results = await $`cd ${PROJECT_ROOT} ; find [^z]* -name *.java -exec grep -q ${substring} {} ';' -exec echo \"******{}******\" ';' -exec grep -${lines} ${substring} {} ';'`;\r\n\r\n// Split filepaths and search results in tokens-array, replace '<' as this confuses html-rendering after span-insertion below\r\nconst tokens = results.toString().replaceAll('<', '<').split('******');\r\n\r\n// build templates from tokens with filepath header and search results in
\r\nconst templates = [];\r\nconst files: string[] = [];\r\nfor (let i = 0; i < tokens.length - 1; i += 2) {\r\n files.push(`${tokens[i + 1]}`);\r\n\r\n // mark substrings in red.\r\n templates.push(`
${tokens[i + 1]}
\r\n
${tokens[i + 2]}
`\r\n .replaceAll(`${substring}`,\r\n `${substring}`)\r\n );\r\n}\r\n\r\n// show the templates => user can scroll in results and then press to continue to dialog for opening project file in IDE\r\nawait div({\r\n html: templates.join('
\\n'),\r\n width: 1200,\r\n height: 700\r\n}, `bg-white text-black text-sm p-2`);\r\n\r\n// put search string in clipboard for use in IDE later\r\nawait copy(substring);\r\n\r\n//---- display buttons in widgets, that let you open IntelliJ Idea -----\r\nconst items = files.map(path => ({\r\n name: path,\r\n // display only shrinked filepath /../ for brevity\r\n display: path.slice(0, path.indexOf('/') + 1) + '..' + path.slice(path.lastIndexOf('/'), path.length)\r\n}));\r\nitems.push({ name: CLOSE_LABEL, display: CLOSE_LABEL });\r\n\r\nconst buttons = `\r\n
\r\n \r\n \r\n
\r\n `;\r\n\r\nlet w = await widget(buttons, {\r\n backgroundColor: '#CCCCAA',\r\n x: 600,\r\n y: Math.max(0, 500 - items.length * 25),\r\n width: 600,\r\n height: items.length * 50 + 50,\r\n state: {\r\n items,\r\n }\r\n});\r\n\r\nw.onClick(async event => {\r\n if (event.dataset.name) {\r\n const path: string = event.dataset.name;\r\n\r\n if (path === CLOSE_LABEL) {\r\n w.close();\r\n exit(0); // process keeps running without..\r\n } else {\r\n // open the project in IntelliJ IDEA\r\n await $`idea ${PROJECT_ROOT}/${path.slice(0, path.indexOf('/'))}`;\r\n // open the specific file chosen inside this project\r\n await $`idea ${PROJECT_ROOT}/${path}`;\r\n // inside IDEA (!) do a search Cmd-F for the substring \r\n // Cmd-V places the substring from Clipboard (where we copied it above) into Ideas Search dialog\r\n await hide();\r\n await keyboard.pressKey(Key.LeftSuper, Key.F);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.F);\r\n await keyboard.pressKey(Key.LeftSuper, Key.V);\r\n await keyboard.releaseKey(Key.LeftSuper, Key.V);\r\n }\r\n }\r\n});```","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-08T19:39:47Z"},{"name":"Normalize GIT branch name","description":"","author":"Marin Muštra","linkedIn":"https://www.linkedin.com/in/marin-mustra","https":"//github.com/microsoft/vscode/blob/main/extensions/git/src/commands.ts#L2182","avatar":"https://avatars.githubusercontent.com/u/13587186?u=94ad9300262f0865a602e4c05246bc0627bd7588&v=4","user":"mmustra","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1097","url":"","title":"Normalize GIT branch name","command":"normalize-git-branch-name","content":"\r\n[Open normalize-git-branch-name in Script Kit](https://scriptkit.com/api/new?name=normalize-git-branch-name&url=https://gist.githubusercontent.com/mmustra/73168d0f629f8b2f526fa45d5068ac12/raw/416c129718d1f9641f6212ba530ab7d6b1f92c06/normalize-git-branch-name.js\")\r\n\r\n```js\r\n// Name: Normalize GIT branch name\r\n// Description: Copy text and paste it to normalized GIT branch name\r\n// Author: Marin Muštra\r\n// LinkedIn: https://www.linkedin.com/in/marin-mustra\r\n\r\nimport '@johnlindquist/kit';\r\n\r\nconst delimiterChar = '-';\r\nconst illegalChar = '';\r\nconst mergableChars = [delimiterChar, illegalChar];\r\nconst shouldLowerCase = true;\r\n\r\nconst input = await paste();\r\nlet branchName = '';\r\n\r\n// Sanitize references\r\n// https://github.com/microsoft/vscode/blob/main/extensions/git/src/commands.ts#L2182\r\n// https://github.com/gitextensions/gitextensions/blob/6eab7392839c4d103bad1581fba5eaf6f008d766/GitCommands/Git/GitBranchNameNormaliser.cs\r\nconst getSanitizedInput = () => {\r\n const edgePattern = /^[-\\s]+|[-\\s]+$/g;\r\n const delimiterPattern = /\\s+|_+|-+/g;\r\n const illegalPattern = /^-+|^\\.|\\/\\.|\\.\\.|~|\\^|:|\\/$|\\.lock$|\\.lock\\/|\\\\|\\*|\\?|@{|^@$|\\.$|\\[|\\]$|^\\/|\\/$/g;\r\n\r\n const isInvalidChar =\r\n (!edgePattern.test(delimiterChar) && illegalPattern.test(delimiterChar)) ||\r\n (!edgePattern.test(illegalChar) && illegalPattern.test(illegalChar));\r\n\r\n if (isInvalidChar) {\r\n throw new Error('Invalid delimiter/illegal character!');\r\n }\r\n\r\n let sanitized = input.trim().replace(delimiterPattern, delimiterChar).replace(illegalPattern, illegalChar);\r\n mergableChars?.forEach((char) => char && (sanitized = sanitized.replace(new RegExp(`\\\\${char}+`, 'g'), char)));\r\n sanitized = sanitized.replace(edgePattern, '');\r\n\r\n return shouldLowerCase ? sanitized.toLowerCase() : sanitized;\r\n};\r\n\r\ntry {\r\n branchName = getSanitizedInput();\r\n\r\n if (!branchName) {\r\n throw new Error('Invalid input!');\r\n }\r\n} catch (error) {\r\n const hint = `${error.message}`;\r\n await editor({ hint, input, description: 'ERROR', readOnly: true, lineNumbers: 'on' });\r\n\r\n exit();\r\n}\r\n\r\nawait setSelectedText(branchName);\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-03T16:21:17Z"},{"name":"Silent Mention","shortcut":"opt x","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1094","url":"https://gist.githubusercontent.com/johnlindquist/9e4885f4e3d80aa0e66f47a727f206c4/raw/7892b042f75e78ba434cf9e7671a5fa275a17350/silent-mention.ts","title":"Silent Mention","command":"silent-mention","content":"Described here: https://twitter.com/peduarte/status/1621187086802980866?s=20&t=a8WakFD64i8W3BPDLIzfWg\r\n\r\n\r\n[Open silent-mention in Script Kit](https://scriptkit.com/api/new?name=silent-mention&url=https://gist.githubusercontent.com/johnlindquist/9e4885f4e3d80aa0e66f47a727f206c4/raw/7892b042f75e78ba434cf9e7671a5fa275a17350/silent-mention.ts\")\r\n\r\n```js\r\n// Name: Silent Mention\r\n// Shortcut: opt x\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet makeSilent = (str: string) =>\r\n str.replace(/[.#@]/g, m => m + \"\\u2060\")\r\n\r\nlet text =\r\n (await getSelectedText()) ||\r\n (await arg(\"Enter text to silent\"))\r\n\r\nlet silentText = makeSilent(text)\r\nawait setSelectedText(silentText)\r\n\r\n```\r\n\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-02T23:54:13Z"},{"name":"Screenshot Current Tweet","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1092","url":"https://gist.githubusercontent.com/johnlindquist/ced2147f9e918c075e058da4b4c3eb2b/raw/6e46ea8b136c6a0300c69d893b808bb5ad6e80e8/screenshot-current-tweet.ts","title":"Screenshot Current Tweet","command":"screenshot-current-tweet","content":"Focus on a tweet like this:\r\n\r\nhttps://twitter.com/film_girl/status/1621170813796851719\r\n\r\n![1621170813796851719](https://user-images.githubusercontent.com/36073/216381005-74c32641-89e3-416e-af7b-77be619c9c66.png)\r\n\r\nThen run this script for a screenshot:\r\n\r\n\r\n[Open screenshot-current-tweet in Script Kit](https://scriptkit.com/api/new?name=screenshot-current-tweet&url=https://gist.githubusercontent.com/johnlindquist/ced2147f9e918c075e058da4b4c3eb2b/raw/6e46ea8b136c6a0300c69d893b808bb5ad6e80e8/screenshot-current-tweet.ts\")\r\n\r\n```js\r\n// Name: Screenshot Current Tweet\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nconst { chromium }: typeof import(\"playwright\") = await npm(\r\n \"playwright\"\r\n)\r\n\r\nlet url = await getActiveTab()\r\nlet timeout = 5000\r\nlet headless = false\r\n\r\nconst browser = await chromium.launch({\r\n timeout,\r\n headless,\r\n})\r\n\r\nconst context = await browser.newContext({\r\n colorScheme: \"dark\",\r\n})\r\nconst page = await context.newPage()\r\npage.setDefaultTimeout(timeout)\r\n\r\nawait page.goto(url)\r\n\r\nlet screenshotPath = home(\r\n \"Downloads\",\r\n path.parse(url).name + \".png\"\r\n)\r\n\r\ntry {\r\n await page\r\n .locator(\"article[tabindex='-1']\")\r\n .screenshot({ path: screenshotPath })\r\n await revealFile(screenshotPath)\r\n log(`Done`)\r\n} catch (error) {\r\n log(error)\r\n}\r\n\r\nawait browser.close()\r\n\r\n```\r\n","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-02T16:19:30Z"},{"name":"Sleep on Shortcode","shortcode":"sl","avatar":"https://avatars.githubusercontent.com/u/36073?u=1617518acc4e480c94d57308adfedebcd936ffd1&v=4","user":"johnlindquist","author":"John Lindquist","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1088","url":"https://gist.githubusercontent.com/johnlindquist/35e843a00efebb5cceeeb65ea45eb779/raw/e83dd71468ff034c9d5f288387457438d1492c38/sleep-on-shortcode.ts","title":"Sleep on Shortcode Example (similar to using an \"alias\")","command":"sleep-on-shortcode-example-similar-to-using-an-alias","content":"To run the script quickly, from the main prompt type:\r\n\r\ns, then l, then space, then y to confirm.\r\n\r\nSo these four characters:\r\n```\r\nsl y\r\n```\r\n\r\nUsing the `//Shortcode: ` metadata will run the script when you hit the \"spacebar\" after a shortcode/alias.\r\n\r\n\r\n[Open sleep-on-shortcode in Script Kit](https://scriptkit.com/api/new?name=sleep-on-shortcode&url=https://gist.githubusercontent.com/johnlindquist/35e843a00efebb5cceeeb65ea45eb779/raw/e83dd71468ff034c9d5f288387457438d1492c38/sleep-on-shortcode.ts\")\r\n\r\n```js\r\n// Name: Sleep on Shortcode\r\n// Shortcode: sl\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\nlet confirm = await arg({\r\n placeholder: `Sleep system?`,\r\n// Script Kit parses hints and assigns single key shortcuts to single letters inside of []\r\n hint: `[y]/[n]`,\r\n})\r\n\r\nif (confirm === \"y\") {\r\n sleep()\r\n}\r\n\r\n```\r\n\r\n\r\n> Sidenote: From the main menu, you can also type `-` to bring up system commands.","extension":".md","dir":"","file":"","description":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-01T22:39:21Z"},{"name":"Theme Maker","description":"","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1087","url":"","title":"Color Picker with saved themes","command":"color-picker-with-saved-themes","content":"\r\n[Open theme-color-picker in Script Kit](https://scriptkit.com/api/new?name=theme-color-picker&url=https://gist.githubusercontent.com/Ambushfall/990b39e9515ac138da5d5c4a5b783f47/raw/19af7566bd72f0e69763b729ee0857a3a1c18130/theme-color-picker.js\")\r\n\r\n```js\r\n// Name: Theme Maker\r\n// Description: Test Themes\r\n\r\nimport \"@johnlindquist/kit\"\r\n\r\n\r\nconst themePath = kenvPath('theme.json');\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\nconst list = [\"foreground\", \"accent\", \"ui\", \"background\"]\r\nconst valueList = [foreground, accent, ui, background, opacity]\r\n\r\n\r\nconst arrayList = list.map((value, index) => {\r\n return { type: \"color\", label: value, value: valueList[index] }\r\n})\r\n\r\narrayList.push({ type: \"range\", label: \"Opacity\", value: opacity })\r\n\r\nawait fields({\r\n onChange: (input, { value }) => {\r\n const [foreground, accent, ui, background, opacity] = value\r\n theme.foreground = foreground\r\n theme.accent = accent\r\n theme.ui = ui\r\n theme.background = background\r\n theme.opacity = opacity\r\n\r\n setTheme(theme);\r\n writeJson(themePath, theme)\r\n },\r\n fields: arrayList,\r\n})\r\n\r\nlet result = await div(md(`# Success!`))\r\n```\r\n","extension":".md","dir":"","file":"","tag":"","section":"","i":"","sectionIndex":"","createdAt":"2023-02-01T21:31:53Z"},{"name":"Widget Theme Picker","description":"","avatar":"https://avatars.githubusercontent.com/u/95249049?u=cd4d1a7a1db7c0a34ae93930545c0a3e32f68b67&v=4","user":"Ambushfall","author":"Ambushfall","twitter":null,"discussion":"https://github.com/johnlindquist/kit/discussions/1086","url":"","title":"Widget Color Picker","command":"widget-color-picker","content":"# Customize your own theme with the color picker\r\n#### Note: Heavily influenced by johnlindquist\r\n\r\n[Open widget-theme in Script Kit](https://scriptkit.com/api/new?name=widget-theme&url=https://gist.githubusercontent.com/Ambushfall/f985c74005580f816a9eaf27852d5902/raw/57c482fec59b1d7ebacde8a2a42010f957af1249/widget-theme.js\")\r\n\r\n```js\r\nimport \"@johnlindquist/kit\"\r\n\r\n// Name: Widget Theme Picker\r\n// Description: Color Picker HTML\r\n\r\nlet themePath = kenvPath(\"theme.json\")\r\n\r\nif (!(await isFile(themePath))) {\r\n let defaultTheme = `{\r\n \"foreground\": \"#ffffff\",\r\n \"accent\": \"#fbbf24\",\r\n \"ui\": \"#343434\",\r\n \"background\": \"#000000\",\r\n \"opacity\": \"0.85\"\r\n }`.trim()\r\n\r\n await writeFile(themePath, defaultTheme)\r\n}\r\n\r\nconst theme = await readJson(themePath)\r\n\r\nconst { foreground, accent, ui, background, opacity } = theme\r\n\r\n\r\nlet w = await widget(\r\n `\r\n