A workspace command framework

Every workspace command.
One source of truth.

Define each operation once in TypeScript. Run it from the terminal, click it in a VS Code dashboard, or let an AI agent invoke it as a tool.

$ pnpm add @ldlework/workmark
view on GitHub →

The problem

Every workspace collects a graveyard.

scripts/deploy.shthe one Ben wrote
package.json scriptshalf aliases, half lies
Makefileonly Alice remembers
docs/ONBOARDING.mddon't actually run step 4
bin/rebuild-assets.tsusage: ???
notion page from 2023out of date, unclear how

Each tool has its own conventions. None of them show up in your editor. None of them are discoverable by an AI assistant. And every new hire learns them by asking in Slack.

The idea

Three for the price of one.

Write a command once in TypeScript. Workmark renders it as a CLI, a VS Code form, and an AI-invocable tool — from the same definition, fully typed.

.wm/commands/build.ts
/** Build one or more packages. */
import { cmd } from "@ldlework/workmark/define";
import { buildable } from "../traits/buildable.js";
export default cmd({
needs: [buildable],
handler: (_, { traits, sh }) =>
sh(traits.buildable.command),
});
terminal
$ wm build api web
--- api ---
compiled in 1.2s
--- web ---
compiled in 0.9s
VS Code
projectapi, web
AI agent
> use_tool("build",
{ project: ["api"] }
)
→ compiled in 1.2s

For monorepos

One command, many projects.

Declare a trait once with a zod schema. Projects fulfill it with typed data. Commands ask for the trait and get per-project data, typed, in the handler.

.wm/traits/docker.ts
import { z } from "zod";
import { defineTrait } from "@ldlework/workmark/define";

export const docker = defineTrait({
  name: "docker",
  schema: z.object({
    composeFile: z.string(),
    service: z.string(),
  }),
});
sites/api/wm.ts
defineProject({
  name: "api",
  has: {
    docker: {
      composeFile: "docker-compose.yml",
      service: "api",
    },
  },
});
sites/web/wm.ts
defineProject({
  name: "web",
  has: {
    docker: {
      composeFile: "docker-compose.yml",
      service: "web",
    },
  },
});

Now one command, and the project enum in CLI, VS Code, and the MCP tool schema is whichever projects fulfill docker.

.wm/commands/up.ts
export default cmd({
  needs: [docker],
  handler: (_, { traits, sh }) =>
    sh(`docker compose -f ${traits.docker.composeFile} up -d ${traits.docker.service}`),
});
terminal
wm up api
wm up api web        # both in parallel
wm up --help         # project: [api, web]

Get started

Three steps.

  1. 1

    Install

    pnpm add -D @ldlework/workmark
  2. 2

    Write a command

    .wm/commands/build.ts
    /** Build the project */
    import { cmd } from "@ldlework/workmark/define";
    
    export default cmd({
      handler: (_, { sh }) => sh("cargo build"),
    });
  3. 3

    Run it

    wm build              # CLI
    # or click it in the VS Code dashboard
    # or let Claude call it as an MCP tool