New Script
Create and manage TypeScript scripts for Script Kit.
Where Scripts Live
~/.scriptkit/plugins/main/scripts/*.ts
Scripts are automatically discovered by Script Kit when saved to this directory.
Creating a New Script
- Create a
.tsfile in~/.scriptkit/plugins/main/scripts/ - Add the SDK import and metadata export
- Save — Script Kit detects it immediately
Minimal Template
import "@scriptkit/sdk";
export const metadata = {
name: "My Script",
description: "What this script does",
};
// Your code here
With Global Hotkey
import "@scriptkit/sdk";
export const metadata = {
name: "Quick Capture",
description: "Capture a quick note",
shortcut: "cmd shift n",
};
const note = await arg("Quick note:");
const filePath = home("notes", `${Date.now()}.txt`);
await Bun.write(filePath, note);
hud("Note saved!");
With Search Alias
import "@scriptkit/sdk";
export const metadata = {
name: "Open Project",
description: "Open a project in VS Code",
alias: "op",
};
const projects = await $`ls ~/projects`.text();
const project = await arg(
"Which project?",
projects.trim().split("\n"),
);
await $`code ~/projects/${project}`;
Script Patterns
Prompt → Transform → Output
import "@scriptkit/sdk";
export const metadata = {
name: "JSON Formatter",
description: "Format JSON from clipboard",
};
const raw = await paste();
try {
const formatted = JSON.stringify(JSON.parse(raw), null, 2);
await copy(formatted);
hud("JSON formatted and copied!");
} catch {
await div(`<div class="p-4 text-red-400">Invalid JSON in clipboard</div>`);
}
Dynamic Choices with Preview
import "@scriptkit/sdk";
export const metadata = {
name: "File Browser",
description: "Browse and open files",
};
const documentsDir = home("Documents");
const files = await $`find ${documentsDir} -maxdepth 2 -type f -name "*.md"`.text();
const file = await arg(
"Open file",
files.trim().split("\n").map((f) => ({
name: f.split("/").pop() || f,
description: f,
value: f,
preview: `<pre class="p-4 text-sm">${f}</pre>`,
})),
);
await open(file);
Multi-Step Workflow
import "@scriptkit/sdk";
export const metadata = {
name: "New Blog Post",
description: "Scaffold a new blog post",
};
const [title, category] = await fields([
{ name: "title", label: "Post Title" },
{ name: "category", label: "Category" },
]);
const slug = title.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
const date = new Date().toISOString().split("T")[0];
const content = `---
title: ${title}
date: ${date}
category: ${category}
---
# ${title}
Write your post here.
`;
const filePath = home("blog", "posts", `${slug}.md`);
await Bun.write(filePath, content);
hud(`Created: ${filePath}`);
Verification
When you create or edit a script from the Tab AI harness, writing the file is not enough. You must verify the actual script inside the current Claude Code terminal session before you report success.
Required loop for every script:
- Save the script to
~/.scriptkit/plugins/main/scripts/<name>.ts - If the script normally uses UI or typed input (
arg,div,editor,fields, etc.), add a non-interactive smoke path behindprocess.env.SK_VERIFY === "1" - Syntax-check / transpile it with Bun:
bun build ~/.scriptkit/plugins/main/scripts/<name>.ts --target=bun --outfile ~/.scriptkit/tmp/test-scripts/<name>.verify.mjs - Execute it with Bun:
SK_VERIFY=1 bun ~/.scriptkit/plugins/main/scripts/<name>.ts - Confirm the stdout, written file, or other observable result matches the request
- If either command fails, fix the script and rerun both commands
- Never report success until both commands pass and the observed behavior is correct
- Only after both commands pass, end your final response with exactly:
Do not emitSCRIPT_READY path=~/.scriptkit/plugins/main/scripts/<name>.ts validated=trueSCRIPT_READYbefore both commands pass.
Verification-Friendly Pattern
Use this when the real script flow is interactive but the harness still needs a terminal-only execution path:
import "@scriptkit/sdk";
export const metadata = {
name: "My Script",
description: "What it does",
};
const isVerify = process.env.SK_VERIFY === "1";
const value = isVerify
? "verification input"
: await arg("What should this script do?");
const output = `Result: ${value}`;
if (isVerify) {
console.log(JSON.stringify({ ok: true, output }));
} else {
await div(`<div class="p-8 text-2xl">${output}</div>`);
}
For UI-heavy requests, the Bun gate is still mandatory. If you also want to manually open Script Kit afterward, do that after the Bun gate — not instead of it.
Prompt Sequencing
Script Kit prompt APIs are stateful interactive surfaces. Never call them concurrently.
- Do not use
Promise.all,Promise.race,Promise.any, orPromise.allSettledwitharg,fields,editor,div,form,drop,find,path,textarea,select, orgrid - Multi-step prompt flows must be sequential
Wrong:
const [url1, url2, url3] = await Promise.all([
arg("URL 1"),
arg("URL 2"),
arg("URL 3"),
]);
Right:
const url1 = await arg("URL 1");
const url2 = await arg("URL 2");
const url3 = await arg("URL 3");
Path Safety
- Prefer
home(...)for user-relative paths such asDocuments,Downloads, and.scriptkit - Use
home(".scriptkit", "kit", "main", ...)when you need the Script Kit workspace explicitly - Do not build user paths from
env.HOME; it may be unset in generated scripts and can produce broken paths likeundefined/...
Sample Input and Expected Output
- Sample file:
~/.scriptkit/plugins/main/scripts/hello-world.ts - Sample command 1:
Expected result: exit code 0; filebun build ~/.scriptkit/plugins/main/scripts/hello-world.ts --target=bun --outfile ~/.scriptkit/tmp/test-scripts/hello-world.verify.mjs~/.scriptkit/tmp/test-scripts/hello-world.verify.mjsexists. - Sample command 2:
Expected stdout:SK_VERIFY=1 bun ~/.scriptkit/plugins/main/scripts/hello-world.ts{"ok":true,"greeting":"Hello, verification!"}
Common Mistakes
- Missing SDK import: Always start with
import "@scriptkit/sdk"; - CommonJS imports: Use ES
importsyntax, never CommonJS - Comment metadata: Use
export const metadata = {...}, not comment-based metadata - Node.js APIs: Use
Bun.file()/Bun.write()/$`cmd`instead offs/child_process - Wrong directory: Scripts must be in
plugins/main/scripts/, notscripts/or elsewhere - Unsafe home paths: Prefer
home(...)overenv.HOMEfor user-relative locations
Related Skills
- new-scriptlet — package scripts as scriptlet bundles
- start-chat — programmatic Agent Chat workflows using the chat SDK
- add-actions — expose script helpers through the Actions Menu
- manage-notes — automate the Notes window from scripts
- new-agent — mdflow-backed agent files (compatibility path)