Metadata-Version: 2.4
Name: flowsh-cli
Version: 0.13.0
Summary: Generate Bash harness scripts from workflow YAML files.
License-Expression: MIT
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: pyyaml>=6,<7
Requires-Dist: pydantic>=2,<3
Requires-Dist: typer>=0.20,<0.21 ; extra == 'cli'
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/tbrandenburg/flowsh
Project-URL: Source, https://github.com/tbrandenburg/flowsh
Provides-Extra: cli
Description-Content-Type: text/markdown

# flowsh-cli

`flowsh-cli` turns workflow YAML into executable shell scripts.

Use it when you want a small, reproducible way to encode a workflow once and rerun it locally or in CI.

## Quick Start

```bash
uvx flowsh-cli path/to/workflows.yml
```

That reads the workflow YAML at the path you provide and writes harness scripts to the current working directory.

In this repository, the example workflow file lives at `workflows.yml`.

Useful flags:

```bash
uvx flowsh-cli path/to/workflows.yml --dry-run
uvx flowsh-cli path/to/workflows.yml --workflow wf_example
uvx flowsh-cli path/to/workflows.yml --force
uvx flowsh-cli path/to/workflows.yml --sidecar dagu
uvx flowsh-cli path/to/workflows.yml --workflow wf_example --output .harness/example.sh --sidecar dagu --dry-run
uvx flowsh-cli path/to/workflows.yml --schema
uvx flowsh-cli --examples
uvx flowsh-cli --example simple
```

You can also run it locally with `uv run flowsh-cli`.

### Optional Dagu sidecar

`--sidecar dagu` writes the normal executable `.sh` plus a sibling `.dagu.yml` with one native Dagu node for each supported source step. Native Dagu v1 supports flat, unconditional `bash` and `agent` steps only. Bash bodies run as independent Bash processes with `-euo pipefail`; agent steps invoke the OpenCode CLI, preserving the literal prompt and optional `agent` selector. The generated graph is linear, has explicit dependencies and no active schedule.

This behavior change is intended for flowsh `0.13.0`: sidecars generated by `0.12.0` and `0.12.1` are whole-harness wrappers and must be regenerated to get native steps. Without `--sidecar dagu`, shell generation and its output bytes are unchanged.

The minimum supported Dagu version is **2.24.0**. The generated `project_dir` default is the absolute generation-time current working directory, which avoids Dagu resolving a relative parameter against the launch directory. Override it at run time when the sidecar is copied to another project or machine. It is an exporter setting, not support for source workflow parameters:

```bash
flowsh-cli .made/workflows.yml --workflow wf_example \
  --output .harness/example.sh --sidecar dagu

dagu validate .harness/example.dagu.yml
dagu start .harness/example.dagu.yml -- project_dir="$PWD"
```

The sidecar does not execute or require the sibling `.sh`, an installed flowsh package, or helper files. It requires Dagu 2.24.0 or newer, Bash, and OpenCode for agent steps, plus the runtime tools and project files used by the source operations. Source shell and prompt text is carried as safely quoted data so Dagu interpolation and the outer shell do not evaluate it prematurely. The source text remains visible in each node's description. Dagu filters host environment variables; operators must explicitly configure `env_passthrough` or dotenv for required runtime variables such as OpenCode provider credentials. Credentials are not copied into the YAML; Dagu logs may still contain prompts and task output.

If native export encounters a source workflow `params`, `vars`, `for`, `while`, or `parallel` step, a nonempty `when`, or an unsupported agent option (`capture`, enabled prompt/field expansion, `model`, `command`, or `dangerouslySkipPermissions: true`), flowsh still generates its ordinary `.sh` and exits successfully but skips that workflow's Dagu sidecar with a diagnostic. In a multi-workflow selection, supported sidecars are still generated. A stale sidecar for a skipped workflow is reported and left untouched, including when `--force` is used. Generation `--dry-run` reports supported output paths and skipped sidecars without writing files; it is distinct from Dagu's `dagu dry` command.

Dagu starts a native run manually; source `schedule` and `enabled` values do not schedule or deploy it. Dagu records status, timings, stdout/stderr and logs for each source step. Failures block later nodes; v1 adds no retries, continuation, capture, state propagation, or unconditional cleanup. Each node is a separate Bash/OpenCode process, so shell state does not carry between nodes. The first Sofathek source Bash step contains `|| return 42`; as in the existing standalone Bash harness, `return` outside a function may exit 2 rather than 42. Flowsh does not reinterpret or repair that source operation.

## Use As A Library

`flowsh-cli` also works as a plain Python library, with no CLI dependencies (no `typer`, no terminal UI). This is the right choice if you want to parse or render workflows from your own Python code instead of shelling out to the CLI.

```bash
uv add flowsh-cli
```

```python
from pathlib import Path
from flowsh_cli import parse_workflows, render_harness, harness_path

workflows = parse_workflows(Path("workflows.yml"))
for workflow in workflows:
    harness_path(workflow).write_text(render_harness(workflow))
```

Public API (re-exported from `flowsh_cli`):

| Name | Purpose |
|---|---|
| `parse_workflows` | Parse and validate a workflow YAML file (given its `Path`) into `Workflow` objects. |
| `Workflow` | Pydantic model of a single workflow. |
| `WorkflowParseError` | Raised on invalid workflow YAML. |
| `render_harness` | Render a `Workflow` into a shell harness script. |
| `harness_path` | Compute the output path for a workflow's harness script. |

If you also want the `flowsh-cli` command-line tool (built on `typer`), install the `cli` extra:

```bash
uv add "flowsh-cli[cli]"
# or, for one-off use without installing:
uvx flowsh-cli path/to/workflows.yml
```

## Workflow Shape

The input file is a single mapping with a `workflows` list:

```yaml
workflows:
  - id: wf_example
    name: Example
    params:
      - name: ISSUE_NUMBER
        description: Issue to inspect
        required: true
    steps:
      - type: vars
        values:
          TODAY: date -u +%F
      - type: bash
        run: printf 'today=%s\n' "$TODAY"
      - type: agent
        agent: general
        model: openai/gpt-5
        command: review
        expandPrompt: true
        prompt: |
          Review issue ${ISSUE_NUMBER} and summarize the repository state.
       - type: parallel
         steps:
           - type: bash
             run: echo "worker A"
           - type: bash
             run: echo "worker B"
       - type: for
         in: ITEMS
         item: ITEM
         steps:
           - type: bash
             run: echo "$ITEM"
       - type: while
         condition: '[ -n "$(ls doc/plan/steps/planned/ 2>/dev/null)" ]'
         steps:
           - type: bash
             run: echo "keep looping until the queue is empty"
```

Harness paths are derived from workflow ids. `wf_example` becomes `example.sh` in the current working directory.

## Step Types

| Type | Purpose | Notes |
|---|---|---|
| `vars` | Execute shell commands and export their stdout into shell variables | Variable names must be uppercase shell identifiers. |
| `bash` | Run shell commands | Runs with `bash -euo pipefail`. |
| `agent` | Call the agent runtime | Supports `agent`, `model`, `command`, `capture`, `expandPrompt`, and `dangerouslySkipPermissions`. |
| `for` | Iterate over newline-delimited values from a previous `vars` step | Flat iteration only; nested `for` steps are not supported. |
| `while` | Re-evaluate a Bash condition before each iteration | Use for dynamic queues or other stateful loops that must keep discovering new work. |
| `parallel` | Run child steps concurrently | Children run as separate branches and the parent waits for all of them. `capture` inside a branch is not visible to sibling branches or later steps — see Agent Behavior below. |

## Agent Behavior

`agent` prompts are literal by default. Use `expandPrompt: true` only when you want `$VAR` or `${VAR}` tokens from earlier `vars` steps substituted at runtime.

`expandPrompt: true` does plain text replacement only. It does not evaluate shell expressions like `$(...)`, backticks, or globs.

Set `capture: VARIABLE_NAME` on an `agent` step when you want the agent's final plain-text answer stored in a shell variable for later `vars` or `bash` steps. The captured value is the answer text (extracted from the underlying `--format json` event stream via `jq`), not the raw JSON stream — requires `jq` to be installed.

Set `dangerouslySkipPermissions: true` only when you want the generated harness to pass `--dangerously-skip-permissions` to the agent runtime. The YAML alias `dangerously-skip-permissions` is also accepted.

Inside a `parallel` step, each child branch runs in its own backgrounded subshell, so `capture` set in one branch is not visible to sibling branches and does not survive past the `parallel` block — there is no gather/join mechanism. To share results across branches (e.g. fan out N agents and merge their verdicts afterward), have each branch write its result to its own file (via a bash instruction in the agent prompt, or a `bash` child step), then read all the files back in a plain `bash` or `vars` step placed after the `parallel` block.

## Validation And Safety

The parser is strict:

- workflow YAML must be UTF-8, non-empty, and under 1 MiB
- the root must be a mapping with a `workflows` key
- duplicate mapping keys and YAML aliases are rejected
- workflow ids must match `wf_[A-Za-z0-9_-]+`
- workflow and step names must be non-empty single-line labels
- unsafe control characters are rejected in executable fields
- `agent` names may contain only letters, digits, `_`, and `-`

Generated harnesses are non-interactive, owner-executable, and refuse to overwrite existing outputs unless `--force` is set. Sidecars are owner-readable/writable only and likewise require `--force` to replace.

Logs go to `.flowsh/logs` by default. Set `FLOWSH_LOG_DIR` to use another relative log directory.

## Development

```bash
uv sync
make install
make qa
make security
make clean
```

`make qa` runs linting, tests, a dependency security audit, and a local build.
`make security` audits hash-pinned runtime, CLI-extra, and development dependencies
exported from `uv.lock` against known vulnerability advisories. It requires network
access and fails if the audit cannot complete. The isolated build backend and
downloaded CI tools are outside this dependency scan.

GitHub Actions are pinned to immutable commits. CI installs OpenCode 1.14.40 from
a versioned Linux release archive and verifies its SHA-256 before extraction.
Workflow comments and prompts are passed as data, not interpolated into shell
scripts; only repository-owner comments can request agent execution. Agent runs
still have their job's declared GitHub permissions, so review untrusted issue
content before authorizing write-capable work.

## Release

```bash
make bump-patch
```

That bumps the patch version, runs QA, and publishes the package.

## Limits

This repository intentionally stays focused on the Python CLI blueprint. It does not include the old TypeScript implementation, workflow templates, DSL explorer, plugin registry, or extra workflow node types.
