@workglow/cli
v0.6.10
Published
Command-line interface example for Workglow, demonstrating how to build and run AI task pipelines from the terminal.
Readme
Workglow CLI Example
A command-line interface for running Workglow AI tasks and workflows.
Overview
The Workglow CLI provides a terminal-based interface for creating, managing, and executing AI task pipelines. It features an interactive task runner with real-time progress visualization, making it easy to run AI workflows from the command line.
A run renders as the graph it is: one row per task, with its status glyph, whatever detail the task is reporting, a progress bar and — once it settles — how long it took, over a bar for the run as a whole and a status line counting completed tasks.

The same commands are also available in a browser, served by this CLI itself with
workglow web. It is the same graph, the same forms, and the same
run — see below.

Features
- Real-time Visualization: Live updates of task execution progress
- Multi-Provider Support: Works with HuggingFace Transformers and TensorFlow MediaPipe
- Local AI Models: Run AI models locally without external API calls
- JSON Configuration: Define workflows using JSON configuration files
Getting Started
Prerequisites
- Bun runtime (recommended) or Node.js 18+
- Terminal with Unicode support for best experience
Installation
bun install @workglow/cliRunning
bun src/workglow.tsUsage
Basic Commands
# Show help
workglow --help
# Run one task by type, passing its config with a single dash
workglow task run Delay -delay 2000
# Run a saved workflow, and list what is saved
workglow workflow list
workglow workflow run my-pipeline
# Serve the same commands in a browser
workglow web
# Serve the registered tasks to MCP clients
workglow mcp serveExample Workflows
Text Generation
workglow generate \
--text "The future of AI is" \
--model "onnx:Xenova/LaMini-Flan-T5-783M:q8" \
--max-length 100Workflow from JSON
Create a workflow.json file:
[
{
"type": "ModelDownload",
"config": {
"model": ["onnx:Xenova/LaMini-Flan-T5-783M:q8"]
}
},
{
"type": "TextRewriter",
"config": {
"text": "The quick brown fox jumps over the lazy dog.",
"prompt": "Rewrite this text to sound like a pirate:"
}
},
{
"type": "DebugLog",
"config": {
"log_level": "info"
}
}
]Then run:
cat workflow.json | workglow jsonCommand Reference
Global Options
--version, -v: Show version information--help, -h: Show help information
Commands
Run workglow --help for the current list; each group has its own --help.
init— create the configuration and directoriesmodel— list, search and manage modelsmcp— manage MCP servers, andmcp servethis CLI's tasks to MCP clientsworkflow— list, add, edit and run saved workflowsagent— the same, for agentstask— list task types and run one by typecredential— manage encrypted credentialsweb— serve the web console described below
Web console
workglow web serves a local page for the same commands the terminal runs:
pick one, fill in its options, press Run, and watch the task graph execute
live. Nothing is duplicated — the command tree is read off the commander
program, the form fields come from the same schemas the terminal prompts
from, and the line at the bottom of the form is exactly what gets executed.
workglow web # http://127.0.0.1:8787/?t=<session token>
workglow web --port 9000The console binds loopback and has no authentication beyond a per-process
session token printed in the URL. Its buttons start runs that spend model and
API quota, so exposing it to a network has to be said out loud (--host),
and the command warns when you do.
How a run works
Each run is a child process of the same binary, spawned with the argv the
page shows you, and handed two extra descriptors: fd 3 for a stream of run
events (NDJSON — one line per task added, status, progress, token usage,
stream chunk) and fd 4 for answers to anything the run asks its operator. The
server replays that stream over SSE, so the page mounts once and is patched
per event, and a reconnect resumes from Last-Event-ID rather than starting
over.
That shape is why the console works for commands nobody wrote it for: the
reporting branch lives in withCli, which every command in this package —
and in every CLI built on it — already runs its graphs through. It also means
a per-run environment override belongs to that run rather than to the server,
and that aborting is the same SIGINT Ctrl-C sends.
Adding the console to your own CLI
import { registerWebCommand } from "@workglow/cli";
registerWebCommand(program, { binaryName: "sec" });Your commands, however deeply nested, appear with nothing to keep in sync.
Contributing UI from a package
Data crosses this seam, never code — a package registers what to show, and the console renders it. There is no client bundle to ship and no plugin loader to keep stable.
import { registerWebPanel, registerWebFieldWidget, registerWebStatusWidget } from "@workglow/cli";
// An extra panel on a finished run's Result tab.
registerWebPanel({
id: "sec:extractions",
title: "Extraction rows",
source: "@workglow/sec",
appliesTo: (invocation) => invocation.path[0] === "spac",
load: async ({ invocation }) => ({
kind: "table",
columns: ["table", "rows"],
rows: await countRowsForIssuer(invocation.args[0]),
}),
});
// A picker for any field whose schema says `format: "sec:cik"`.
registerWebFieldWidget({
format: "sec:cik",
source: "@workglow/sec",
search: async (query) => findIssuers(query),
});
// A meter in the rail.
registerWebStatusWidget({
id: "sec:edgar",
title: "EDGAR fetch budget",
source: "@workglow/sec",
read: async () => [{ label: "req/s", value: currentRate(), max: 8 }],
});A panel that throws is reported as a panel rather than taking the page down, and a status widget that cannot answer is dropped from the rail.
A command whose real input lives in a schema can say so, which is how
task run and workflow run get their forms:
import { registerCommandSchemaProvider } from "@workglow/cli";
registerCommandSchemaProvider({
path: ["spac", "process"],
resolve: async (args) => ({ input: schemaForIssuer(args[0]), config: undefined }),
});MCP server
workglow mcp serve offers this CLI's registered tasks to MCP clients as
tools, over Streamable HTTP. One tool per task type, named for the registered
type itself — TextGenerationTask, which is what task list prints with the
Task suffix trimmed — with the task's own input schema as the tool's
arguments and its output as the result. Nothing is duplicated: a task registered through
registerTasks is a tool for the same reason it is a task run argument.
workglow mcp serve # http://127.0.0.1:8788/mcp
workglow mcp serve --port 9100 --path /
workglow mcp serve --task TextGeneration --task DelayIt binds loopback and requires a bearer token, generated per process and printed at startup along with a ready-made client config:
mcp server listening on http://127.0.0.1:8788/mcp — 114 tasks offered as tools
bearer token: 3nS2...
client config: {"type":"http","url":"http://127.0.0.1:8788/mcp","headers":{"Authorization":"Bearer 3nS2..."}}A client config has to hold the same token across restarts, so pin one with
WORKGLOW_MCP_TOKEN (preferred — an argument that never changes is one every
other process can read out of ps) or --token. --no-auth drops the gate
entirely, which anything that can reach the port can then walk through;
exposing the server with --host warns for the same reason the web console
does.
Flow-control and hidden tasks are left out — a MapTask with no subgraph
around it is not a tool anyone can call, and "Hidden" is what a class that
named no category gets, which covers JsonTask (it runs a graph handed to it,
so publishing it would put every excluded task back within reach) and
LambdaTask (its config is a function, which no client can send). --task
narrows the list to exactly what you name, that filter included.
A task that asks a person asks the client driving the call, through MCP
elicitation, rather than the terminal nobody is sitting at: each tool call
runs against its own registry carrying an McpElicitationConnector bound to
that call. A client that did not advertise elicitation gets a tool error
saying so instead of a call that never returns.
Serving MCP from your own host
The parts worth sharing live in @workglow/mcp/server, not here:
createTaskMcpServer builds the tool surface over any transport,
startMcpHttpServer hosts it from node:http, McpSessionRouter holds the
Streamable HTTP sessions for a host that already has its own web framework,
and authorizeBearer is the token check.
import { createTaskMcpServer, generateBearerToken, startMcpHttpServer } from "@workglow/mcp/server";
const handle = await startMcpHttpServer({
port: 8788,
host: "127.0.0.1",
token: generateBearerToken(),
createServer: () => createTaskMcpServer({ name: "my-app", version: "1.0.0" }),
});Configuration
Model Configuration
The CLI automatically downloads and caches AI models. You can configure model settings:
Development
Project Structure
src/
├── workglow.ts # Main CLI entry point
├── TaskCLI.ts # CLI command definitions
├── TaskGraphToUI.ts # Terminal UI components
├── components/ # Reusable CLI components
├── lib.ts # Library exports
└── worker_hft.ts # HuggingFace workerAdding New Commands
- Define the command in
TaskCLI.ts:
program
.command("my-command")
.description("My custom command")
.option("-t, --text <text>", "Input text")
.action(async (options) => {
// Command implementation
});- Implement the command logic using Workglow workflows:
const workflow = new Workflow();
workflow.MyCustomTask(options);
await workflow.run();Available Models
HuggingFace Transformers (ONNX)
Text Generation:
onnx:Xenova/LaMini-Flan-T5-783M:q8onnx:Xenova/distilgpt2:q8
Translation:
onnx:Xenova/m2m100_418M:q8onnx:Xenova/opus-mt-en-de:q8
Classification:
onnx:Xenova/distilbert-base-uncased:q8onnx:Xenova/roberta-base-sentiment:q8
TensorFlow MediaPipe
- Text Embeddings:
mediapipe:universal-sentence-encoder
Performance
- Model Caching: Models are cached after first download
- Quantized Models: Use quantized models (q8) for better performance
Troubleshooting
Common Issues
Model Download Failures:
# Clear model cache rm -rf ~/.cache/Memory Issues:
# Use smaller models or increase system memory workglow generate --model "onnx:Xenova/distilgpt2:q8"
Examples
Batch Processing
Process multiple files:
for file in *.txt; do
workglow generate "$(cat $file)" > "${file%.txt}_generated.txt"
donePipeline Processing
Chain multiple operations:
# Generate text, then translate it
workglow generate --text "Write about AI" | \
workglow rewrite --prompt "Rewrite this text to sound like a pirate:"License
Apache 2.0 - See LICENSE for details.
