The problem
Every developer accumulates a folder of one-off scripts. A Python file that exports records, a Node script that migrates a value between two systems, a shell script that prods an API and dumps the result. They do the job, but each one is structured slightly differently and none of them are legible six months later.
The real cost is the setup. Authentication, retries, chaining data between steps, error handling, types for API responses. You either invest in all of it upfront or you write something sloppy and move on. It’s usually the latter.
What it does
DTK is a CLI that scaffolds a properly structured TypeScript project for writing runbooks: multi-step workflows expressed through a fluent builder API.
await suite()
.dynamo({ region: process.env.AWS_REGION! })
.step("fetch-records", async (ctx) => {
return ctx.services.dynamo.queryItems(process.env.TABLE_NAME!, {
KeyConditionExpression: "entity = :e",
ExpressionAttributeValues: { ":e": { S: "Product" } },
});
})
.step("process-records", async (ctx) => {
const { items } = ctx.outputs["fetch-records"] as { items: Record<string, unknown>[] };
// do something with items
})
.run("throwOnError");
Each step receives a context object carrying the outputs of every previous step, an HTTP client with built-in retry logic, helpers for OAuth 2.0, Basic and Bearer auth, file system operations, and any service plugins you’ve added.
Plugins are added with a single command (dtk add aws-dynamo, dtk add open-ai, and so on). Each one copies the service implementation and its types into your project, wires it into the suite builder, appends the required environment variables and installs the relevant package. The injection is idempotent, so running it twice is safe.
The design decision that matters
The generated project only has the dependency it needs. The core project has very little. However there are “plugin” commands to run that extend DTK. Once you’ve scaffolded and added your plugins, you own every file. Extend them, delete them, or ignore DTK entirely from that point on. It’s an opinionated scaffolder, not a framework you’re locked into.
That rules out a whole category of problems. There’s no version to keep in step, no upgrade path to manage, and no risk of the tool going stale and taking your workflows with it. If the generated DynamoDB service is missing a method you need, you open the file and add it.
That said every new version will provide an upgrade path if you need one and you’ve heavily customised your version.
Where it fits
It comes up against tools like Zapier, Make and n8n, but they’re a different category. Those platforms are built around connectors and triggers, they store credentials on someone else’s infrastructure, and the workflow lives in their system rather than yours.
DTK is for developers who want the workflow to be TypeScript sitting in their own repository, tracked in GIT, running locally or in CI, reviewable in a pull request like anything else. If a product manager needs a Slack notification when an Airtable row changes, DTK is the wrong tool. If you need to script something that touches DynamoDB, calls an external API and pushes a message to SQS, it’s the right one. I often think of these as executeable “runbooks”.
The other case it handles well is sharing scripted procedures across teams. Support and development teams can run the same runbook without needing the domain knowledge that a folder of ad-hoc scripts demands.
Contributing
People are welcome to use and contribute if they want. I’ve put some general guidance, issues and several example PRs to see how it works. The only ask is that you follow the standard set within the codebase already.
I’m also not massively against AI contributers either but this repo is naturally geared towards developers and used by developers, and as such any changes need to be very well understood.
