@taskflow-corp/cli

Multi-agent orchestration harness for AI coding agents (claude-code, codex, cursor, opencode, pi). Async-await TypeScript API; lifecycle hooks; auto-todos with verify-loop.
Install
npm install @taskflow-corp/cli
Quick start
import { taskflow } from '@taskflow-corp/cli';
await taskflow('hello').run(async ({ phase, session }) => {
await phase('greet', async () => {
await session('say-hi', { with: 'claude-code', task: 'Print hello world' });
});
});
Install harnesses from anywhere — taskflow add
Shadcn-style distribution: drop any harness into any project with a single command. You never need to install the package locally — npx @taskflow-corp/cli@latest <command> works from any directory and always pulls the newest version.
Source formats (every form accepted by add)
npx @taskflow-corp/cli@latest add example-hello
npx @taskflow-corp/cli@latest add @acme/e2e-video-tests
npx @taskflow-corp/cli@latest add @acme/e2e-video-tests@^1.2.0
npx @taskflow-corp/cli@latest add https://example.com/r/harness.json
npx @taskflow-corp/cli@latest add https://raw.githubusercontent.com/you/repo/main/r/x.json
npx @taskflow-corp/cli@latest add ./my-harness.json
npx @taskflow-corp/cli@latest add /abs/path/harness.json
npx @taskflow-corp/cli@latest add ~/shared/harness.json
npx @taskflow-corp/cli@latest add user/repo
npx @taskflow-corp/cli@latest add user/repo/path/to/item.json
npx @taskflow-corp/cli@latest add user/repo/path/to/item.json
npx @taskflow-corp/cli@latest add github:user/repo/items/foo.json
npx @taskflow-corp/cli@latest add gitlab:user/repo/items/foo.json
npx @taskflow-corp/cli@latest add bitbucket:user/repo/items/foo.json
npx @taskflow-corp/cli@latest add git::https://host/org/repo.git//items/foo.json?ref=v1
npx @taskflow-corp/cli@latest add git::ssh://git@github.com/you/priv.git//items/foo.json?ref=main
npx @taskflow-corp/cli@latest add https::https://example.com/bundle.json?sha256=abc123
npx @taskflow-corp/cli@latest add file::./local/harness.json
Detection order (first match wins, mirrors shadcn's resolver):
- ends with
.json and not a URL → local file
git:: / https:: / file:: prefix → fully qualified
github: / gitlab: / bitbucket: prefix → host shortcut
- parses as a URL → raw URL
@ns/name → namespace lookup
user/repo[/subpath][#ref] → bare GitHub shortcut
- otherwise → bare name (built-in
@taskflow registry)
Every form resolves to: fetch → validate (Zod) → write files → patch .agents/taskflow/config.ts (ts-morph AST merge) → merge .env.local → upsert taskflow.lock.
Multiple sources in one call
npx @taskflow-corp/cli@latest add example-hello @acme/video-tests ./local.json
registryDependencies from each item are resolved transitively (BFS + Kahn topo sort with cycle tolerance), so one command can pull a whole dependency graph.
Lifecycle commands
taskflow init | Scaffold taskflow.json + .agents/taskflow/config.ts + harness/rules dirs (auto-invoked by add on first use) |
taskflow add <source...> | Install one or more harnesses |
taskflow view <source> | Print the resolved registry-item JSON (no write, no install) |
taskflow list | List installed harnesses from taskflow.lock |
taskflow search <query> | Fuzzy-match local registries + auto-discover taskflow harnesses on GitHub |
taskflow update [name...] | Re-resolve; rewrite files + lockfile (--all implied if no names) |
taskflow remove <name> | Delete installed files + lockfile entry |
taskflow apply <preset> | add --overwrite alias (shadcn-style re-skin) |
taskflow build [input] | Publisher: inline source file contents, emit r/*.json + r/registry.json |
taskflow mcp | Start MCP server over stdio (tools: list_harnesses, search, install) |
taskflow run <harness.ts> | Execute an installed harness (TUI if TTY else JSONL) |
taskflow watch <harness.ts> | Alias for run |
taskflow plan <harness.ts> | Static AST preview — no LLM calls |
Flag reference for add
-y, --yes | false | Skip all confirmation prompts. On existing-file conflicts, skips the file (does NOT auto-overwrite — use --overwrite for that). |
-o, --overwrite | false | Replace existing files without prompt. Orthogonal to --yes. |
--dry-run | false | Resolve + validate, print what would change, do not write. |
--diff | false | Like --dry-run with a diff. Implies --dry-run. |
--view | false | Resolve and print the registry-item JSON to stdout. Do not write, do not preflight. |
-p, --path <dir> | harnessDir from taskflow.json | Override install directory for this run. |
-c, --cwd <dir> | process.cwd() | Run as if invoked from <dir>. |
-s, --silent | false | Mute all @clack/prompts output. On conflicts, skips like --yes. |
--frozen | false | CI mode: error if resolved items don't match taskflow.lock. |
--skip-adapter-check | false | Skip the requiredAdapters preflight check. |
Getting started in any project (30 seconds)
cd /path/to/some/project
npx @taskflow-corp/cli@latest add <your-source> -y --skip-adapter-check
npx @taskflow-corp/cli@latest list
npx @taskflow-corp/cli@latest run .agents/taskflow/harness/<name>.ts
File layout after init + add
project/
├── taskflow.json # config + registries map
├── taskflow.lock # content-addressed install manifest
├── .env.local # auto-loaded; ${VAR} expansion for registries
└── .agents/
└── taskflow/
├── config.ts # hooks, plugins, scope
├── harness/<name>.ts # installed harness files
├── harness/plugins/<name>.ts # installed plugins
├── harness/utils/<name>.ts # installed utilities
├── harness/examples/<name>.ts
└── rules/<name>.md # installed rules files
Private registries with auth
taskflow.json:
{
"$schema": "https://taskflow.sh/schema/taskflow.json",
"version": "1",
"registries": {
// simple form — string URL template
"@acme": "https://registry.acme.com/r/{name}.json",
// advanced form — per-registry headers and query params
"@private": {
"url": "https://api.corp.com/taskflow/{name}.json",
"headers": { "Authorization": "Bearer ${TASKFLOW_TOKEN}" },
"params": { "v": "latest" }
}
}
}
{name} placeholder is mandatory; {style} is reserved for future use.
${VAR_NAME} (braces required, no $VAR form) interpolates from process.env.
.env and .env.local are auto-loaded before resolution.
- Missing env vars fail pre-flight with a clear
RegistryMissingEnvironmentVariablesError.
- HTTP 401/403/404/410 map to specific typed errors with actionable hints.
Publishing your own registry
- Author
registry/registry.json:
{
"$schema": "https://taskflow.sh/schema/registry.json",
"name": "@yourname",
"homepage": "https://yourname.dev",
"items": [
{
"$schema": "https://taskflow.sh/schema/registry-item.json",
"name": "my-harness",
"type": "taskflow:harness",
"files": [
{ "path": "items/my-harness.ts", "type": "taskflow:harness" }
]
}
]
}
- Write the source
.ts files referenced by files[].path.
- Run
npx @taskflow-corp/cli@latest build -c ./registry --output ./registry/r.
- Host the emitted
r/ directory (GitHub Pages, S3, your CDN).
- Consumers then:
npx @taskflow-corp/cli@latest add https://<your-host>/r/my-harness.json
See registry/ in this repo for a worked example.
Auto-discovery — zero-config installs from any GitHub repo
Typing taskflow add <user>/<repo> with no subpath and no registry-item.json at the repo root now triggers auto-discovery: the CLI searches the target repo for files that look like taskflow harnesses (i.e. import @taskflow-corp/cli / taskflow-cli / taskflowjs / taskflow-sdk and top-level-call taskflow(...).run(...)), then prompts a multi-select so you pick exactly which harnesses to install.
$ npx @taskflow-corp/cli@latest add AbhiShake1/taskflow
◇ Discovered 4 taskflow harnesses in AbhiShake1/taskflow:
◇ Select harnesses to install: (space to toggle, enter to confirm)
◼ examples/ui-plan.ts (matched: import from '@taskflow-corp/cli')
◻ examples/ui-execute.ts
◼ examples/ui-harness-trio/index.ts
◻ harness/self-evolve.ts
◇ Installing 2 harnesses…
✔ .agents/taskflow/harness/ui-plan.ts
✔ .agents/taskflow/harness/index.ts
✔ Done. 2 written, 0 skipped, 0 overwritten.
Rules:
- 0 matches → error with a clear message ("no taskflow harnesses found in
<repo>").
- 1 match → auto-install, no prompt.
- >1 matches → interactive
@clack/prompts multiselect. Default nothing selected; pick what you want.
--yes with >1 matches → errors ("multiple harnesses found; re-run without --yes, or pass the full path e.g. user/repo/path/file.ts to target one file"). Auto-discovery never silently installs everything just because --yes was passed.
If the target repo ships a registry-item.json at HEAD on the default branch, the original Tier 2 shortcut behaviour wins (fetch the registry item directly) and discovery is skipped. If you type the full path (user/repo/sub/path.ts), the tarball-fetch path is used and discovery is also skipped.
How it works. The CLI hits a small Cloudflare Pages Function (/api/discover) which calls the official GitHub Code Search REST API. Results are cached 10 minutes in Workers KV. Sub-second end-to-end, no headless browsers, no cookie juggling — just a plain server-to-server fetch. Set TASKFLOW_DISCOVER_URL to point the CLI at a private proxy (useful for enterprise mirrors or local wrangler pages dev):
TASKFLOW_DISCOVER_URL=https://my-proxy.example.com/api/discover \
npx @taskflow-corp/cli@latest add AbhiShake1/taskflow
taskflow search uses discovery too. Results from a taskflow search <query> now fold GitHub-wide discovery hits in alongside the configured-registry fuzzy-matches, so you can grep the whole GitHub index for a harness by keyword without targeting a specific repo.
Full design notes for discovery live in docs/add-command-plan.md §15.
Item types (registry-item.json → type)
taskflow:harness | <harnessDir>/<basename> | Main .ts the user runs |
taskflow:plugin | <harnessDir>/plugins/<basename> | Imported from config.ts |
taskflow:utils | <harnessDir>/utils/<basename> | Shared TS |
taskflow:example | <harnessDir>/examples/<basename> | Sample invocation |
taskflow:rules | <rulesDir>/<basename> or target | Markdown rules |
taskflow:config-patch | Merged into config.ts via ts-morph AST | Not a file |
taskflow:file | target (required) | Arbitrary path incl. ~/ |
MCP integration (Claude Code / Cursor / Codex)
npx @taskflow-corp/cli@latest mcp
Exposes three tools over stdio:
list_harnesses — return installed harnesses from taskflow.lock
search — fuzzy-match local registries + GitHub-wide auto-discovery
install — delegate to runAdd
Wire it into your MCP client config and the model can discover and install harnesses autonomously.
CI usage
npx @taskflow-corp/cli@latest add <source> --yes --silent --frozen
Full design notes: docs/add-command-plan.md.
Hooks via .agents/taskflow/config.ts
import { defineConfig } from '@taskflow-corp/cli/config';
export default defineConfig({
events: {
afterTaskDone: async (ctx, { spec, result }) => {
},
},
todos: { autoExtract: true, maxRetries: 3 },
});
Structured output
Sessions with a zod schema return typed results. claude-code uses native tool-use. codex (on gpt-5 / gpt-5.4 models) uses --output-schema; gpt-5-codex variants fall back to prompt-engineered JSON pending openai/codex#4181. Other adapters (cursor, opencode, pi) use prompt-engineered JSON. Override codex behavior with HARNESS_CODEX_SCHEMA=0|1.
import { z } from 'zod';
const result = await session('summary', {
with: 'codex:gpt-5.4',
task: 'Summarize the repo',
schema: z.object({ title: z.string(), bullets: z.array(z.string()) }),
});
Plugins
A plugin contributes hooks, a ctx.plugins.<name> namespace, and optional config fragments:
import type { Plugin } from '@taskflow-corp/cli/core';
export const myPlugin: Plugin = () => ({
name: 'my-plugin',
events: {
afterTaskDone: async (ctx, { spec }) => { },
},
ctx: () => ({ hello: () => 'world' }),
});
To get typed access to ctx.plugins.myPlugin.hello() in downstream hooks, module-augment the plugin namespace registry:
declare module '@taskflow-corp/cli/core' {
interface PluginNamespaces {
'my-plugin': { hello: () => string };
}
}
Examples
Single-file harnesses live in examples/. Install the SDK globally first (npm i -g @taskflow-corp/sdk), then run any file directly with tsx:
examples/ui-plan.ts — scans a project, injects missing data-testids, emits a YAML test plan.
examples/ui-execute.ts — consumes the YAML and generates a standalone Playwright project.
examples/ui-execute.test.ts — unit tests for the YAML → Playwright codegen.
examples/ui-content.ts — stitches media into a video, generates narratives, and publishes to YouTube / Facebook / Instagram / LinkedIn / Twitter / Medium / Reddit (stubs — swap in real OAuth before production).
Each is ~400–600 LOC with zero build step: tsx examples/ui-plan.ts.
Environment variables
ANTHROPIC_API_KEY | Required for claude-code sessions and pi:anthropic/*. |
OPENAI_API_KEY, GROQ_API_KEY, CEREBRAS_API_KEY, GEMINI_API_KEY | Required per-session for the respective providers. |
HARNESS_PI_BIN | Override the pi binary name (default pi). Use omp with @oh-my-pi/pi-coding-agent. |
HARNESS_CODEX_SCHEMA | 0 forces prompt-engineered JSON for codex; 1 forces native --output-schema. |
HARNESS_ADAPTER_OVERRIDE=mock | Swap every agent for the mock adapter — smoke runs with zero token cost. |
HARNESS_NO_TTY=1 | Force headless JSONL output even when a TTY is attached. |
HARNESS_RUNS_DIR=... | Override the runs archive directory (default data/runs). |
HARNESS_REAL_TESTS=1 | Enable integration tests that make real LLM calls (default-skipped). |
Docs
See .claude/skills/taskflow/SKILL.md for the authoring guide.