Metadata-Version: 2.4
Name: flowsh-cli
Version: 0.12.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` containing that exact generated harness. Without the option, generation is unchanged. Generation `--dry-run` lists both paths but writes neither. The generated Dagu workflow has no schedule and must be started manually; MADE remains responsible for schedule/enabled policy.

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

# Ordinary harness arguments are a JSON string array encoded as base64.
ARGS_B64="$(python3 -c 'import base64,json; print(base64.b64encode(json.dumps(["value with spaces", "", "two\nlines"], ensure_ascii=False).encode()).decode())')"
dagu start .harness/example.dagu.yml --params "{\"project_dir\":\"$PWD\",\"harness_args_b64\":\"$ARGS_B64\"}"
```

The sidecar does not need the sibling `.sh` or an installed flowsh package at execution time. It requires Dagu 2.24.0-compatible built-in `js.run` and `file.write` actions, Bash, and the original harness's runtime tools (for example OpenCode and `jq` when used), plus the target project's files and referenced prompts. Dagu's environment filtering may require operator configuration (`env_passthrough`/dotenv) for workflow parameters and external environment dependencies; credentials are never copied into the YAML.

The harness runs as one Bash process in the selected project directory. Runtime `project_dir` overrides the generation-time relative default. Its runtime `$0`/`BASH_SOURCE` identifies Dagu's private materialized copy, not the original generated path. Dagu's overall start status reports orchestration success/failure and is not guaranteed to equal the harness's raw exit number. Dagu logs may contain prompts, arguments, and task output: base64 is transport, not encryption; never pass secrets in arguments or expose private workflow logs publicly. This compatibility wrapper does not translate steps, freeze external prompt files, or deploy/activate a DAG.

## 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 clean
```

`make qa` runs linting, tests, and a local build.

## 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.
