Skip to main content

CLI API

@flow-state-dev/cli — Terminal interface for running flows, executing blocks, and inspecting definitions.

Commands

fsdev run <flowKind> <action>

Execute a flow action with streaming NDJSON output.

fsdev run my-agent chat -i '{"message": "Hello!"}'

Options:

FlagDescription
-i, --input <json>Inline JSON input
-f, --input-file <path>JSON input from file
-m, --model <model>Override model for all generator blocks
-s, --session <id>Session ID for reuse across invocations
--seed-session <json|path>Seed session-level state (JSON or file path)
--seed-user <json|path>Seed user-level state
--seed-project <json|path>Seed project-level state
--flow-dir <path>Override flow discovery root (repeatable)
--format <format>Output format (default: json)

NDJSON events:

Each line of stdout is a JSON object with a type field:

{"type":"item_added","item":{"id":"...","type":"message","role":"assistant"}}
{"type":"content_delta","itemId":"msg_1","delta":"Hello there!"}
{"type":"state_change","scope":"session","resourcePath":"counter","changeType":"update"}
{"type":"flow_complete","output":{"reply":"Hello there!"},"durationMs":1234,"items":3}
Event typeWhen it fires
item_addedA new output item (message, reasoning, tool call, etc.) was created
content_deltaIncremental content chunk for a streaming item
state_changeA scope state or resource was modified
flow_completeThe action completed successfully
errorThe action failed

Session reuse:

# State persists between invocations with the same --session
fsdev run stateful increment -i '{"increment": 1}' --session my-session
# → {"count": 1}

fsdev run stateful increment -i '{"increment": 1}' --session my-session
# → {"count": 2}

fsdev dev

Start an HTTP dev server that serves the flow API and DevTool UI together.

fsdev dev

Options:

FlagDescription
-p, --port <port>Port to listen on (default: 4200)
--flow-dir <path>Override flow discovery root (repeatable)
-m, --model <model>Override model for all generator blocks
--no-openDon't open the browser automatically

Requires: @flow-state-dev/devtool installed (provides the pre-built UI assets).

The server discovers flows, registers them in an in-memory flow registry, creates filesystem stores at .fsdev/data/, and starts listening. API routes are served at /api/flows/*. The DevTool UI is served for all other paths. See DevTool Setup for full details.

fsdev block <specifier>

Execute a single block in isolation using the testing harness.

fsdev block ./src/flows/my-app/blocks/counter.ts -i '{"increment": 1}'

Options:

FlagDescription
-i, --input <json>Inline JSON input
-f, --input-file <path>JSON input from file
-m, --model <model>Model override for generator blocks
--format <format>Output format (default: json)

Output:

{
"success": true,
"block": { "kind": "handler", "name": "counter" },
"output": { "count": 1 },
"schemaValidation": {
"input": { "passed": true },
"output": { "passed": true }
},
"execution": { "durationMs": 12 }
}

Flow Discovery

fsdev run discovers flows automatically from these directories (relative to cwd):

src/flows/<flow-name>/flow.ts   → default exports a FlowInstance
flows/<flow-name>/flow.ts → default exports a FlowInstance
flows/<flow-name>.ts → direct file export

Monorepo support

In monorepo structures, the CLI also scans one level of subdirectories under packages/, examples/, and apps/:

packages/*/src/flows/
packages/*/flows/
examples/*/src/flows/
apps/*/src/flows/

This means flows defined anywhere in your monorepo are automatically discoverable without configuration.

Custom flow directories

Use --flow-dir to override default discovery with explicit paths. This is repeatable:

fsdev run my-flow action -i '{}' \
--flow-dir ./packages/api/src/flows \
--flow-dir ./shared/flows

When --flow-dir is specified, only the given directories are searched — the default and monorepo scanning is skipped.

Error messages

When a flow or action isn't found, the error lists what was discovered and where it searched:

Flow "chat" not found. Available flows: echo, stateful, my-agent
Searched: src/flows, flows, examples/hello-chat/src/flows

Programmatic API

The CLI exports its utilities for use in scripts and CI:

discoverFlows(cwdOrOptions?)

Scan conventional directories and return all discovered flow instances. Accepts a string (cwd) or an options object.

import { discoverFlows } from "@flow-state-dev/cli";

// Simple: scan from a directory
const flows = await discoverFlows("./my-project");

// With options: explicit directories
const flows2 = await discoverFlows({
cwd: "./my-project",
flowDirs: ["packages/api/src/flows", "shared/flows"],
});

resolveFlow(specifier)

Load a single flow from an explicit file path.

import { resolveFlow } from "@flow-state-dev/cli";

const flow = await resolveFlow("./src/flows/my-chat/flow.ts");

resolveBlock(specifier)

Load a single block from a file path.

import { resolveBlock } from "@flow-state-dev/cli";

const block = await resolveBlock("./src/blocks/counter.ts");

parseInputArg(options)

Parse input from --input or --input-file flags.

formatOutput(value, format)

Format a value for terminal output.

Type Exports

import type {
FlowRunResult, // Structured result from fsdev run
FlowEvent, // NDJSON event union type
BlockExecResult, // Structured result from fsdev block
} from "@flow-state-dev/cli";

Exit Codes

CodeMeaning
0Success
1Execution error (flow or block failed at runtime)
2Invalid arguments (bad JSON input, missing required flags)
3Configuration error (invalid port, missing devtool assets)
4Discovery error (flow or block not found, import failed)
10Internal error (unhandled exception)