Dispatch is a small Bun-powered command dispatcher for repository workflows.
It gives every project one stable entry point, dispatch, while keeping the real project behavior owned by the project. Dispatch provides common defaults for checks, sync, ports, process inspection, and setup, but repo-local scripts, config, and command files always have a path to take over.
Dispatch is meant to standardize how people and agents interact with a repo without forcing every repo to use the same implementation.
It can:
- add a small set of package scripts that point at
dispatch - create or update a Dispatch-managed block in the target repo's
AGENTS.md - add a shared oxlint config and a basic CI workflow
- run common quality commands like
lint,typecheck,test,check, andci - provide safer repo maintenance commands like
sync-careful,portclean, andprocesses - hand off to repo-owned commands for app-specific workflows
It does not:
- replace project-specific scripts unless
dispatch init --forceis used - infer a deployment platform
- hide destructive sync behavior behind a safe-sounding command
- require every quality command to be added to
package.json
The normal bootstrap command is:
bunx @memoir/dispatchRunning the package this way executes dispatch in the current repo. If Dispatch is not already initialized there, it runs the setup path.
Setup may update the target repo by:
- adding
@memoir/dispatchtodevDependencies - adding the small managed script set to
package.json - creating or patching
AGENTS.md - writing
.oxlintrc.json - writing
.github/workflows/ci.yml - running the detected package manager install command
Preview setup without writing files:
bunx @memoir/dispatch --dry-runWrite files but skip the dependency install step:
bunx @memoir/dispatch --no-installRefresh Dispatch-managed files later:
dispatch initForce replacement of conflicting managed scripts:
dispatch init --forceUse --force carefully. It is appropriate after moving repo-specific behavior into dispatch.config.ts or .dispatch/commands/*.ts; it is not a safe way to blindly overwrite existing project scripts.
Installing @memoir/dispatch only installs the npm package and exposes the dispatch binary through node_modules/.bin.
It does not automatically patch the consuming repo.
The repo is changed by running:
dispatch initor by invoking the package as a bootstrap command:
bunx @memoir/dispatchAfter initialization, package scripts such as bun dev or bun sync call the dispatch binary from node_modules/.bin. The command implementation still comes from the installed package unless the repo overrides it with local config, local command files, or unmanaged package scripts.
dispatch init keeps package.json intentionally small. It manages only common human workflow aliases:
{
"scripts": {
"dev": "dispatch dev",
"sync": "dispatch sync",
"sync-careful": "dispatch sync-careful",
"portclean": "dispatch portclean",
"update-all": "dispatch update-all",
"dp": "dispatch dp",
"menu": "dispatch menu"
}
}People can then use normal package-manager commands:
bun dev
bun sync
bun sync-careful
bun portclean
bun update-all
bun dpQuality commands stay available through dispatch without adding more package-script noise:
dispatch lint
dispatch typecheck
dispatch test
dispatch check
dispatch ciIf the repo appears to use Convex, dispatch init also adds:
{
"scripts": {
"convex": "dispatch convex",
"convex:dev": "dispatch convex dev",
"convex:deploy": "dispatch convex deploy"
}
}dispatch init creates or updates the consuming repo's root AGENTS.md.
The important behavior is that the target codebase gets instructions when it runs setup. The package having its own AGENTS.md is not enough.
Dispatch writes only this managed block:
<!-- dispatch:agents:start -->
...
<!-- dispatch:agents:end -->User-authored content outside the block is preserved. On later runs, Dispatch replaces only the managed block.
If AGENTS.md contains only one marker, or markers are in the wrong order, setup refuses to continue. That prevents Dispatch from guessing and overwriting user content.
Check the state with:
dispatch doctorDoctor reports agent instructions as missing, stale, malformed, or OK.
Dispatch resolves commands in this order:
dispatch.config.tscommand override- project command file
- unmanaged package script
- built-in command
This order is what lets Dispatch provide defaults while still letting each repo own the behavior that matters.
Create dispatch.config.ts at the repo root:
import type { DispatchConfig } from "@memoir/dispatch";
const config: DispatchConfig = {
ports: [3000, 3001, 5173],
appFilter: "@my/app",
deployScript: "deploy:prod",
scriptAliases: {
deploy: ["deploy:prod"],
},
commands: {
menu: ["bun", "run", "--cwd", "apps/runner", "menu"],
},
};
export default config;Use commands when you want to fully override a Dispatch command with an argv array or shell string. Use scriptAliases when a Dispatch command should call one of the repo's package scripts.
Project command files live at:
.dispatch/commands/<name>.tsExample:
package.json "sync": "dispatch sync"
.dispatch/commands/sync.ts repo-specific implementationA command file can export an argv array:
export default ["git", "fetch", "origin"];Or it can run Bun code directly:
export default async () => {
const proc = Bun.spawn(["git", "status", "--short"], {
stdout: "inherit",
stderr: "inherit",
});
const exitCode = await proc.exited;
if (exitCode !== 0) process.exit(exitCode);
};dispatch dev
dispatch start
dispatch preview
dispatch menuThese commands prefer repo-owned behavior. For example, dispatch dev runs a configured command, a local command file, an unmanaged dev script, or a Turbo dev task when appropriate.
dispatch lint
dispatch fix
dispatch lint:fix
dispatch typecheck
dispatch test
dispatch check
dispatch ci
dispatch build
dispatch verify
dispatch preparedispatch check runs the normal local confidence path:
lint -> typecheck -> testdispatch ci runs the CI confidence path:
lint -> typecheck -> test -> build when the repo has an unmanaged build scriptIf the repo already defines an unmanaged script for one of these commands, Dispatch runs that script. Otherwise it uses standard behavior where possible:
lintuses the package-ownedoxlintbinary with the repo.oxlintrc.jsontypecheckruns the repo-localtscwhen TypeScript evidence existstestdetects Vitest, Jest, or Node test files
dispatch init
dispatch doctor
dispatch install
dispatch sync
dispatch sync-careful
dispatch port
dispatch portclean
dispatch processes
dispatch clean
dispatch update
dispatch update-all
dispatch scripts
dispatch opsdispatch sync is intentionally destructive. It fetches origin, hard-resets to origin/main by default, and removes untracked files.
Preview it:
dispatch sync --dry-runTarget a different branch:
dispatch sync --branch releasedispatch sync-careful is the safe update path. It fetches, refuses uncommitted changes, refuses local commits that are not on the remote target, and only fast-forwards.
dispatch port and dispatch portclean clean common local dev ports and stale Next.js dev locks.
dispatch processes lists processes that appear to belong to the current repo. Use --json for machine-readable output and --deep for more process metadata.
dispatch scripts lists raw package.json scripts.
dispatch ops lists operational scripts and can run a specific script:
dispatch ops list
dispatch ops deploy:prod
dispatch ops env:vercel:syncdispatch deploy
dispatch dp
dispatch deploy --skip-checksDeploy is deliberately unopinionated. Dispatch does not infer Vercel, npm publishing, servers, environments, or deployment targets.
dispatch deploy runs checks first, then hands off only to repo-owned deploy behavior:
dispatch.config.tscommands.deploydispatch.config.tsdeployScriptdispatch.config.tsscriptAliases.deploy- a plain unmanaged package script named
deploy
If none of those exists, Dispatch exits and tells you to configure deploy or run a specific script with dispatch ops <script-name>.
Use --skip-checks only when you intentionally want to bypass the confidence path.
dispatch convex dev
dispatch convex deployIf convex is installed in the repo, Dispatch runs the local tool through the detected package manager. Otherwise it uses the package manager's one-off runner.
Dispatch itself requires Bun. Project scripts are run with the package manager detected from the target repo:
packageManager field
pnpm-lock.yaml
package-lock.json / npm-shrinkwrap.json
yarn.lock
bun.lock / bun.lockb
default: bunIf the packageManager field conflicts with a lockfile, Dispatch refuses to guess.
Dispatch tries to be boring and predictable:
- user-authored
AGENTS.mdcontent is preserved outside Dispatch markers - partial marker state is treated as a conflict
- existing package scripts are preserved unless they are already managed by Dispatch or
--forceis used - deploy has no platform fallback
syncis labeled as destructive and supports--dry-runsync-carefulexists for the common safe update case
Run the local CLI:
bun run devList commands:
bun run listRun the normal verification path:
bun run checkIndividual checks:
bun run lint
bun run typecheck
bun run testFor changes to initialization or agent-instruction behavior, run:
bun run test
bun run typecheck
bun run src/cli.ts init --force --dry-runUse the broader check before release:
bun run checkPreview npm package contents:
bun run pack:dry-runVerify a packed install in a temporary repo:
bun run smoke:packPublish:
npm publish --access publicMIT