typescript-script-developer
Skill1 starsChanged 4 months agoMITView on GitHub
Write production-ready TypeScript CLI tools, automation scripts, and batch file processors with strict type-checking — TypeScript 5.x with strict mode tsconfig, ESM, type-safe argument parsing via node:util parseArgs (with discriminated-union argv types), structured stderr logging, typed Error subclasses for distinct failure modes, exhaustiveness checks via 'never', node:fs/promises with branded types where useful, top-level await, graceful SIGINT/SIGTERM handling, and cross-platform support (Linux, macOS, Windows). Run scripts via tsx (default), Bun, or Deno; build for distribution with tsc. Targets Node.js 20 LTS and above plus TypeScript 5.4+. Use this skill whenever the user asks to create a TypeScript / TS / .ts script, typed Node CLI tool, automation, batch processor, or data pipeline — including casual phrasings like 'write a typescript script that ...', 'automate this in ts', 'make me a typed CLI', or 'I need a one-off TS processor'. Also use when reviewing or hardening an existing TypeScript script.
In this repository24 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- architecture-auditSkill
- autodreamSkill
- deep-discoverySkill
- generic-by-designSkill
- gitops-cd-developerSkill
- gitops-pipeline-developerSkill
- golang-script-developerSkill
- humanizerSkill
- information-architectureSkill
- interactive-deck-builderSkill
- javascript-script-developerSkill
- linux-script-developerSkill
- makefile-script-developerSkill
- plan-interviewSkill
- pre-commit-setupSkill
- python-script-developerSkill
- qaSkill
- skill-builderSkill
- teacher-modeSkill
- terraform-module-developerSkill
- ui-deslopSkill
- xquik-x-dataSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
17 rules. About 0 are common, 3 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 47
Every script you write starts from this skeleton.
- Very specific: line 135
Confirm the runtime / build setup: Default: Node.js 20+ run via tsx for development, built with tsc for distribution.
- Very specific: line 142
Validate the result: Run node scripts/validate-script.mjs <your-script.ts> — checks shebang, TSDoc, import type, node: prefix, explicit …
- Very specific: line 166
tsxstrips types — it does not type-check: Run tsc --noEmit separately. - Very specific: line 168
No
ascasts to escape the type system: Use type guards or zod. The double-cast as unknown as T always hides a bug. - Very specific: line 170
__dirnameis undefined in ESM: Use dirname(fileURLToPath(import.meta.url)). - Very specific: line 178
Start every script from assets/templates/.
- Very specific: line 179
Use ESM (import, node: prefix).
- Very specific: line 184
Use unknown in catch blocks.
- Very specific: line 185
Use assertNever to enforce exhaustiveness on switches over discriminated unions.
- Very specific: line 186
Use parseArgs from node:util.
- Very specific: line 188
Run validate-script.mjs and tsc --noEmit against the result.
- Very specific: line 193
Use any, or as any, or as unknown as T to escape the type system.
- Very specific: line 194
Use TS enums — prefer as const literal unions.
- Distinctive: line 196
Use sync fs APIs in the main flow.
- Very specific: line 198
Use // @ts-ignore — use @ts-expect-error with a comment instead.
- Distinctive: line 203
Write CommonJS in new code.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
TypeScript Script Developer
Production-ready TypeScript. Strict tsconfig + ESM + parseArgs with discriminated-union argv + typed Error subclasses + exhaustiveness via never + node:fs/promises + graceful signals.
When to use
- The user asks for any
.tsscript, typed Node CLI, automation, or batch processor. - The user wants to harden, refactor, or review an existing TypeScript script.
- A task chain ends in "and put it in a TS script".
Skip this skill for: plain JavaScript scripts (use javascript-script-developer), browser-only code, framework-bound code (Next.js, Express apps), or libraries (different shape).
Required structure
Every script you write starts from this skeleton. Do not omit the entry guard, the typed catch, or the structured error class hierarchy.
#!/usr/bin/env node
/**
* <one-line description>.
*
* Usage: tsx script.ts [options] <input>
* Exit: 0 ok, 1 generic, 2 usage, 3 input, 130 interrupt
*/
import { parseArgs } from "node:util";
import { fileURLToPath } from "node:url";
import process from "node:process";
interface CliArgs {
readonly input: string;
readonly verbose: boolean;
}
abstract class ScriptError extends Error {
abstract readonly kind: string;
abstract readonly exitCode: number;
}
class UsageError extends ScriptError {
readonly kind = "usage" as const;
readonly exitCode = 2;
}
let verbose = false;
const log = {
debug: (...a: readonly unknown[]): void => {
if (verbose) process.stderr.write(`[debug] ${a.join(" ")}\n`);
},
info: (...a: readonly unknown[]): void =>
process.stderr.write(`[info] ${a.join(" ")}\n`),
error: (...a: readonly unknown[]): void =>
process.stderr.write(`[error] ${a.join(" ")}\n`),
} as const;
async function main(): Promise<number> {
const { values, positionals } = parseArgs({
options: {
verbose: { type: "boolean", short: "v" },
help: { type: "boolean", short: "h" },
},
allowPositionals: true,
strict: true,
});
if (values.help) {
/* print help */ return 0;
}
if (positionals.length !== 1)
throw new UsageError("expected exactly one positional argument");
const args: CliArgs = {
input: positionals[0]!,
verbose: values.verbose ?? false,
};
verbose = args.verbose;
process.on("SIGINT", () => process.exit(130));
if (process.platform !== "win32")
process.on("SIGTERM", () => process.exit(143));
// ... real work ...
return 0;
}
const isEntry = process.argv[1] === fileURLToPath(import.meta.url);
if (isEntry) {
main()
.then((code) => process.exit(code ?? 0))
.catch((err: unknown) => {
if (err instanceof ScriptError) {
log.error(err.message);
process.exit(err.exitCode);
}
log.error("unexpected:", err instanceof Error ? err.stack : err);
process.exit(1);
});
}Workflow
- Confirm the package manager. See
references/package-managers.md. Save the answer to memory on first run. - Confirm the runtime / build setup. Default: Node.js 20+ run via
tsxfor development, built withtscfor distribution. If the user mentions Bun or Deno, loadreferences/runtimes-and-build.md. - Generate / verify
tsconfig.jsonusingassets/templates/tsconfig.template.json. Always strict, alwaysnoUncheckedIndexedAccess, alwaysverbatimModuleSyntax. - Pick a starting template from
assets/templates/:script.template.ts— single-purpose script.cli-tool.template.ts— multi-subcommand CLI.file-processor.template.ts— batch processing with bounded concurrency, typed pool.
- Apply patterns from
references/patterns.mdfor typed argv, discriminated-union errors, exhaustiveness checks, branded paths, async generators, typed pool, env validation with zod. - Validate the result. Run
node scripts/validate-script.mjs <your-script.ts>— checks shebang, TSDoc,import type,node:prefix, explicitmainreturn type, type/interface declarations, Error subclass withexitCode, noany, noas any, parseArgs, stderr logging, no sync fs, entry-guard, SIGINT. Aim for ≥ 90%. - Type-check separately.
npx tsc --noEmit—tsxonly strips types, it doesn't check them. - Cross-check
references/anti-patterns.md— especiallyany,as any, missingimport type,enum(preferas constunions),// @ts-ignore(prefer@ts-expect-error). - For cross-platform scripts, load
references/cross-platform.mdand apply the rules. - Generate man-page-style docs using
references/documentation.md— TSDoc on public APIs, plus a<script>.mdreference.
Available resources
assets/templates/{script,cli-tool,file-processor}.template.ts— starting points.assets/templates/tsconfig.template.json— strict tsconfig with all the right flags.assets/examples/csv-analyzer.ts— full-featured reference (typed CSV streaming, gzip, discriminated-union errors, Welford running stats).scripts/validate-script.mjs— score a TS script against the checklist (run after writing).references/patterns.md— load when implementing typed argv, errors, branded types, exhaustiveness, env, async generators, concurrency.references/anti-patterns.md— load when reviewing or rewriting an existing TS script.references/cross-platform.md— load when targeting Windows or macOS alongside Linux.references/runtimes-and-build.md— load when picking tsx vs Bun vs Deno, building with tsc, or shipping a single binary.references/package-managers.md— load on first run to capture the user's preferred installer.references/documentation.md— load when generating man-page-style docs / TSDoc.
Top gotchas (always inline — do not skip)
- Strict tsconfig is non-negotiable.
strict: true+noUncheckedIndexedAccess: true. Anything less makes TypeScript a slower JavaScript. import typefor type-only imports. WithverbatimModuleSyntax: true, the compiler enforces this. It also stops accidental runtime imports of types..jsextension in ESM imports. Even when importing a.tsfile. The compiler doesn't rewrite paths; the runtime reads.js.tsxstrips types — it does not type-check. Runtsc --noEmitseparately. CI must do both.unknown, neverany.anyopts out of type-checking. Everyanyis a bug waiting.- No
ascasts to escape the type system. Use type guards orzod. The double-castas unknown as Talways hides a bug. catch (err)infersunknown. Narrow withinstanceof Error,err instanceof ScriptError, or a custom type guard before using.__dirnameis undefined in ESM. Usedirname(fileURLToPath(import.meta.url)).- Discriminated-union errors with
kindplusassertNever(default)for exhaustiveness. The compiler will tell you when you forget a case. - Subclass
Errorwithreadonly exitCode: number. Map subclasses to codes in the top-level catch. as constunions overenum. TS enums have weird runtime semantics;as constplus a union type is cleaner and tree-shakable.// @ts-expect-errornot@ts-ignore. The compiler tells you when the suppression is no longer needed.
What you DO
- Start every script from
assets/templates/. Always with the matchingtsconfig.template.json. - Use ESM (
import,node:prefix). Useimport typefor type-only imports. - Define an
interfacefor parsed argv. Validate types at the parseArgs boundary. - Define an abstract
ScriptErrorbase class; subclass per failure mode withkindliteral +readonly exitCode. - Type
main(): Promise<number>explicitly. - Annotate every public function's parameters and return type. Lean on inference inside.
- Use
unknownin catch blocks. Narrow withinstanceofor a guard before using. - Use
assertNeverto enforce exhaustiveness on switches over discriminated unions. - Use
parseArgsfromnode:util. Reach for commander/yargs only for complex CLIs. - Log to stderr. Reserve stdout for structured output.
- Run
validate-script.mjsandtsc --noEmitagainst the result. - Pin TypeScript to a major version in
package.json.
What you do NOT do
- Use
any, oras any, oras unknown as Tto escape the type system. - Use TS
enums — preferas constliteral unions. - Forget the
.jsextension in ESM imports of local.tsfiles. - Use sync fs APIs in the main flow.
- Type-cast where a type guard or
zodparse would work. - Use
// @ts-ignore— use@ts-expect-errorwith a comment instead. - Mix
importandimport typerandomly — turn onverbatimModuleSyntax. - Throw strings or non-Error objects.
- Skip running
tsc --noEmitbecause "tsx ran fine". - Pin minor versions of TypeScript in production — the major-version pin is the contract.
- Write CommonJS in new code. ESM only.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

