Install
$ agentstack add skill-flowcmd-skill-flowcmd-author ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Authoring flowcmd workflows
flowcmd runs deterministic YAML workflows of shell steps. Each step is a /bin/sh -c command. Steps pass data by reading earlier steps' stdout through {{ steps.NAME.output }} templates. flowcmd has no built-in LLM abstraction — if a step needs a model, you invoke whatever CLI or HTTP endpoint produces text on stdout.
This skill is the source of truth for writing valid flowcmd YAML. If a field or syntax isn't documented here, it does not exist — do not invent it.
When flowcmd is the right tool
Reach for flowcmd when the user wants:
- A reproducible pipeline that mixes shell commands and one or more LLM calls
- Conditional steps (skip the commit if there's nothing staged)
- Parallel fan-out (run three linters at once, wait, then summarise)
- Retries with a delay for flaky network calls
Prefer a plain shell script when the work is linear and has no conditionals or parallelism. Prefer a Makefile when the user thinks in targets and dependencies.
Minimum viable workflow
name: Hello
steps:
- name: greet
run: echo "hello from flowcmd"
Two required fields at the top (name, steps) and two required fields per step (name, run). Everything else is optional.
The complete schema (every legal field)
name: Workflow Title # required, free-form string
description: One-line summary. # optional
steps: # required, at least one entry
- name: step-name # required. Regex: ^[a-z][a-z0-9_-]*$, max 64 chars, unique
description: What this does. # optional
run: | # required. Shell script run via `/bin/sh -c`.
echo "can be multi-line" # Supports {{ steps.X.output }} interpolation.
when: "{{ steps.prev.exitcode == 0 }}" # optional. If the rendered value
# is "", "false", or "0" → skip.
parallel: true # optional, default false. Adjacent parallel:true
# steps form a single concurrent group.
retry: # optional. If present:
attempts: 3 # must be >= 1
delay: 5s # Go duration: "500ms", "5s", "1m30s"
That is the entire surface. These fields do NOT exist — do not emit them:
shell: · cmd: · env: · working_dir: · cwd: · timeout: · outputs: · continue_on_error: · needs: · depends_on: · on: · vars: · inputs: · matrix: · image: · uses:
If the user wants to set an env var, do it inside run: with standard shell: run: FOO=bar ./script.sh. Step commands inherit the user's environment, so API keys exported in their shell are already visible.
Template syntax
Templates are wrapped in {{ }} and may appear anywhere inside run: or when:.
Reference a step's result:
{{ steps.step-name.output }} # stdout, whitespace-trimmed
{{ steps.step-name.error }} # stderr, whitespace-trimmed
{{ steps.step-name.exitcode }} # integer, as a string ("0", "1", …)
{{ steps[0].output }} # zero-indexed positional reference
{{ steps[2].exitcode }}
Compare against a quoted literal (only inside when: is this useful):
{{ steps.foo.output != '' }}
{{ steps.bar.exitcode == '0' }}
{{ steps[0].output == "success" }}
Only == and != are supported. The result renders as the string "true" or "false".
What templates CANNOT do:
- No filters:
{{ x | upper }}❌ - No env access:
{{ env.HOME }}❌ — use$HOMEinsiderun:instead - No arithmetic:
{{ a + b }}❌ - No boolean ops:
{{ a && b }}❌ - No forward references: a step can only reference steps that appear earlier in the file. Self-references are rejected.
Validation rules that commonly burn agents
flowcmd validates at parse time. These failures stop execution before any step runs:
- Step
namemust match^[a-z][a-z0-9_-]*$(lowercase, digits,-,_, starting with a letter).MyStep,step 1,1stare all invalid. - Step
namemust be unique within the workflow. runis required and non-empty.- Every
{{ … }}expression must contain at least onesteps.*reference. Bare literals like{{ hello }}are rejected. - A template reference to a step that comes later in the file is rejected with "does not precede this step".
retry.attemptsmust be ≥ 1.- Workflow needs at least one step.
Always re-read the YAML after writing it and mentally walk through these checks before handing it to the user.
Parallel groups
A run of adjacent steps with parallel: true forms one parallel group. They start simultaneously, and flowcmd waits for every step in the group to finish before moving to the next group.
steps:
- name: setup # sequential
run: ./prep.sh
- name: lint # ─┐
run: ./lint.sh # │
parallel: true # │ these three run concurrently
# │ in one group
- name: test # │
run: ./test.sh # │
parallel: true # │
# │
- name: build # ─┘
run: ./build.sh
parallel: true
- name: publish # sequential, runs only after all three above finish
run: ./publish.sh
Cancellation: if any step in a parallel group fails, the group's shared context is cancelled — the remaining steps in that group receive a cancellation signal and stop. The workflow then fails. Downstream groups do not run.
A single step with parallel: true (no siblings) is legal but pointless — it behaves sequentially.
when semantics
when: is rendered the same way as run:, then the result string is tested:
| Rendered value | Outcome | |---|---| | "" (empty) | skip | | "false" | skip | | "0" | skip | | anything else ("true", "1", "hi", ...) | execute |
Typical patterns:
when: "{{ steps.diff.output != '' }}" # run only if diff produced output
when: "{{ steps.tests.exitcode == '0' }}" # run only if tests passed
when: "{{ steps.flag.output }}" # run if a prior step printed anything
when is evaluated before retry. A step skipped by when never attempts retries.
Retry semantics
retry:
attempts: 3 # total attempts (NOT additional retries)
delay: 5s # wait between attempts; cancelled if the workflow is cancelled
A step is considered successful the first time it returns exit code 0. Non-zero exits and execution errors count as failures; the engine waits delay, then tries again, up to attempts total.
Calling an LLM from a step
flowcmd has no native LLM concept. A step calls a model by running whatever CLI or HTTP client produces text on stdout.
# local runner
- name: classify
run: ollama run llama3 "Classify this: {{ steps.body.output }}"
# vendor CLI
- name: summarise
run: claude -p "Summarise: {{ steps.diff.output }}"
# raw HTTP
- name: explain
run: |
curl -s https://api.example.com/v1/complete \
-H "Authorization: Bearer $API_KEY" \
-d "{\"prompt\": \"Explain: {{ steps.err.output }}\"}" \
| jq -r '.text'
Full per-backend recipes (Ollama, Claude CLI, llama.cpp server, Hugging Face inference, Unsloth-served, OpenAI, custom) live in the sibling flowcmd-llm-recipes skill and in examples/ next to this file.
Three things that constantly trip agents up when scripting LLM calls inside YAML:
- JSON inside YAML inside sh. When interpolating
{{ … }}into a JSON body, the step's output may contain quotes or newlines that break your JSON. Pipe throughjq -Rs .or write the prompt to a file first:
``yaml run: | printf '%s' "{{ steps.diff.output }}" > /tmp/prompt.txt jq -Rs '{prompt: .}' /tmp/prompt.txt | curl -s … --data-binary @- ``
- Multi-line
run:with a leading pipe. Userun: |(literal block scalar) for any script that contains newlines, backticks, or significant whitespace.
- API keys. They come from the user's shell environment (
$OPENAI_API_KEY,$HF_TOKEN, etc.) — never hardcode them in the workflow. If the key isn't set, the step will fail; add a guard step if you want a friendly error.
The workflow you should follow when writing a flowcmd file
- Confirm the user's intent and the LLM backend (if any) before generating YAML. If they didn't say, ask.
- Sketch the steps in plain English first, then translate to YAML. One step = one shell command or pipeline.
- Write the file into the user's local workflow directory:
.flowcmd/.yml. If the user wants it globally available across projects, write to~/.flowcmd/.ymlinstead. - Validate it: run
flowcmd validate .flowcmd/.yml. Fix any reported errors before declaring done. - If
flowcmdisn't on the user's PATH, invoke the flowcmd-install skill rather than skipping validation. - Show the user how to run it:
flowcmd run(no.ymlextension, no path — flowcmd resolves the name against./.flowcmd/then~/.flowcmd/).
Anti-patterns — never emit these
- ❌
env:,working_dir:,timeout:,outputs:,continue_on_error:,needs:,uses:— none exist - ❌
{{ env.FOO }}— use$FOOinsiderun: - ❌
{{ x | upper }},{{ len(x) }}, any Jinja filter or function - ❌ Referencing a later step (or the current step)
- ❌
PascalCaseorcamelCasestep names — must be kebab/snake lowercase - ❌ Hardcoding API keys in
run: - ❌ A bare
{{ something }}that doesn't contain asteps.*reference — parser rejects it
References
Deeper detail lives in sibling files (read on demand):
references/schema.md— every field, exact Go struct definitionsreferences/template-syntax.md— the template grammar, verbatim from the enginereferences/validation-rules.md— everything the parser rejects, with error messagesreferences/execution-model.md— parallel groups, cancellation, retry, when
Working examples (every one is validated in CI against the real flowcmd binary):
examples/hello.yml— sequential + parallel + templatesexamples/commit.yml— Claude CLI generates a commit message,whenguards the commitexamples/ollama.yml— local Ollama summarisationexamples/llama-cpp.yml— llama.cpp HTTP serverexamples/hf-inference.yml— Hugging Face inference APIexamples/unsloth-served.yml— an OpenAI-compatible server (Unsloth, vLLM, etc.)examples/openai.yml— raw OpenAI chat completionsexamples/parallel-fanout.yml— concurrent linters with a summarising tail stepexamples/retry-when.yml— retry on flaky calls plus a conditional follow-up
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: flowcmd
- Source: flowcmd/skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.