flowsh-cli 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowsh-cli
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Generate Bash harness scripts from workflow YAML files.
5
5
  License-Expression: MIT
6
6
  Classifier: Programming Language :: Python :: 3.11
@@ -46,6 +46,19 @@ Supported step types are only `vars`, `bash`, and `agent`.
46
46
 
47
47
  The input path must be a regular file no larger than 1 MiB. The input file must be valid UTF-8, non-empty YAML with a mapping root, no duplicate mapping keys, and no YAML aliases. Workflow and step names are single-line labels. Executable fields reject unsafe control bytes while allowing normal newlines and tabs. `vars` keys must be uppercase shell variable names, and `agent` names may contain only letters, digits, `_`, and `-`.
48
48
 
49
+ Agent prompts are literal by default. Set `expandPrompt: true` on an `agent` step only when the prompt should be expanded by Bash at harness runtime, for example to insert values exported by earlier `vars` steps:
50
+
51
+ ```yaml
52
+ - type: agent
53
+ name: Fix captured issue
54
+ agent: general
55
+ expandPrompt: true
56
+ prompt: |
57
+ Follow issue #$ISSUE_NUMBER.
58
+ ```
59
+
60
+ `expandPrompt: true` is a security-sensitive opt-in: Bash also performs command substitution such as `$(...)` and backticks in the prompt body before OpenCode receives it. Keep it disabled for prompts that contain shell examples or untrusted content.
61
+
49
62
  Harness paths are derived from workflow ids. `wf_example` writes `.harness/example.sh`.
50
63
 
51
64
  ## Commands
@@ -65,6 +78,9 @@ uvx flowsh-cli .made/workflows.yml --force
65
78
 
66
79
  # Show version
67
80
  uvx flowsh-cli --version
81
+
82
+ # Show the workflow YAML schema
83
+ uvx flowsh-cli --schema
68
84
  ```
69
85
 
70
86
  You can also run it via `uv run flowsh-cli` if installed locally.
@@ -88,6 +104,7 @@ Options:
88
104
  --dry-run Print planned output paths without writing scripts.
89
105
  --force Overwrite existing files. Without this, existing files cause a failure.
90
106
  --version Show the flowsh-cli version and exit.
107
+ --schema Show the workflow YAML schema and exit.
91
108
  --help Show this message and exit.
92
109
  ```
93
110
 
@@ -100,6 +117,7 @@ Exit codes:
100
117
  |---|---:|---|---|
101
118
  | `--help` | `0` | Help text | Empty |
102
119
  | `--version` | `0` | `flowsh-cli <version>` | Empty |
120
+ | `--schema` | `0` | Workflow schema as YAML-formatted JSON Schema | Empty |
103
121
  | Valid generation | `0` | One `Wrote <path>` line per harness | Empty |
104
122
  | Valid `--dry-run` | `0` | One `DRY-RUN would write <path>` line per selected workflow | Empty |
105
123
  | Missing required CLI argument | `2` | Empty | Typer usage error |
@@ -112,7 +130,7 @@ Generated harnesses are also non-interactive. `harness.sh --dry-run` exits `0` a
112
130
 
113
131
  Generated harnesses are written with owner-only executable permissions and refuse to overwrite existing paths unless `--force` is passed. Multi-workflow generation preflights overwrite conflicts before writing any harness. `--force` replaces regular harness files and harness-file symlinks, but never replaces a directory at a harness file path. The `.harness` output directory must be a real directory, not a symlink or file. Harness dry runs do not create log files or directories. Real harness logs go to `.flowsh/logs` by default with owner-private directory and file permissions. Set `FLOWSH_LOG_DIR` when running a harness to use another local relative log directory; absolute paths, `..` path segments, symlinked path components, and non-directory log paths are refused. Logging setup and write failures fail the harness instead of being silently ignored.
114
132
 
115
- Generated `bash` and `vars` bodies run with `bash -euo pipefail`, so command failures stop the workflow instead of being masked by later successful commands. Captured `vars` values are exported for later `bash` steps. `agent` steps invoke only `opencode run --format json -- <prompt>` with optional `--agent <agent>` before `--`, so dash-prefixed prompts are message content rather than OpenCode flags. Agent steps fail with a clear error if `opencode` is not on `PATH`.
133
+ Generated `bash` and `vars` bodies run with `bash -euo pipefail`, so command failures stop the workflow instead of being masked by later successful commands. Captured `vars` values are exported for later `bash` steps. `agent` prompt heredocs are quoted by default and unquoted only when `expandPrompt: true` is set. `agent` steps invoke only `opencode run --format json -- <prompt>` with optional `--agent <agent>` before `--`, so dash-prefixed prompts are message content rather than OpenCode flags. Agent steps fail with a clear error if `opencode` is not on `PATH`.
116
134
 
117
135
  ## Development
118
136
 
@@ -30,6 +30,19 @@ Supported step types are only `vars`, `bash`, and `agent`.
30
30
 
31
31
  The input path must be a regular file no larger than 1 MiB. The input file must be valid UTF-8, non-empty YAML with a mapping root, no duplicate mapping keys, and no YAML aliases. Workflow and step names are single-line labels. Executable fields reject unsafe control bytes while allowing normal newlines and tabs. `vars` keys must be uppercase shell variable names, and `agent` names may contain only letters, digits, `_`, and `-`.
32
32
 
33
+ Agent prompts are literal by default. Set `expandPrompt: true` on an `agent` step only when the prompt should be expanded by Bash at harness runtime, for example to insert values exported by earlier `vars` steps:
34
+
35
+ ```yaml
36
+ - type: agent
37
+ name: Fix captured issue
38
+ agent: general
39
+ expandPrompt: true
40
+ prompt: |
41
+ Follow issue #$ISSUE_NUMBER.
42
+ ```
43
+
44
+ `expandPrompt: true` is a security-sensitive opt-in: Bash also performs command substitution such as `$(...)` and backticks in the prompt body before OpenCode receives it. Keep it disabled for prompts that contain shell examples or untrusted content.
45
+
33
46
  Harness paths are derived from workflow ids. `wf_example` writes `.harness/example.sh`.
34
47
 
35
48
  ## Commands
@@ -49,6 +62,9 @@ uvx flowsh-cli .made/workflows.yml --force
49
62
 
50
63
  # Show version
51
64
  uvx flowsh-cli --version
65
+
66
+ # Show the workflow YAML schema
67
+ uvx flowsh-cli --schema
52
68
  ```
53
69
 
54
70
  You can also run it via `uv run flowsh-cli` if installed locally.
@@ -72,6 +88,7 @@ Options:
72
88
  --dry-run Print planned output paths without writing scripts.
73
89
  --force Overwrite existing files. Without this, existing files cause a failure.
74
90
  --version Show the flowsh-cli version and exit.
91
+ --schema Show the workflow YAML schema and exit.
75
92
  --help Show this message and exit.
76
93
  ```
77
94
 
@@ -84,6 +101,7 @@ Exit codes:
84
101
  |---|---:|---|---|
85
102
  | `--help` | `0` | Help text | Empty |
86
103
  | `--version` | `0` | `flowsh-cli <version>` | Empty |
104
+ | `--schema` | `0` | Workflow schema as YAML-formatted JSON Schema | Empty |
87
105
  | Valid generation | `0` | One `Wrote <path>` line per harness | Empty |
88
106
  | Valid `--dry-run` | `0` | One `DRY-RUN would write <path>` line per selected workflow | Empty |
89
107
  | Missing required CLI argument | `2` | Empty | Typer usage error |
@@ -96,7 +114,7 @@ Generated harnesses are also non-interactive. `harness.sh --dry-run` exits `0` a
96
114
 
97
115
  Generated harnesses are written with owner-only executable permissions and refuse to overwrite existing paths unless `--force` is passed. Multi-workflow generation preflights overwrite conflicts before writing any harness. `--force` replaces regular harness files and harness-file symlinks, but never replaces a directory at a harness file path. The `.harness` output directory must be a real directory, not a symlink or file. Harness dry runs do not create log files or directories. Real harness logs go to `.flowsh/logs` by default with owner-private directory and file permissions. Set `FLOWSH_LOG_DIR` when running a harness to use another local relative log directory; absolute paths, `..` path segments, symlinked path components, and non-directory log paths are refused. Logging setup and write failures fail the harness instead of being silently ignored.
98
116
 
99
- Generated `bash` and `vars` bodies run with `bash -euo pipefail`, so command failures stop the workflow instead of being masked by later successful commands. Captured `vars` values are exported for later `bash` steps. `agent` steps invoke only `opencode run --format json -- <prompt>` with optional `--agent <agent>` before `--`, so dash-prefixed prompts are message content rather than OpenCode flags. Agent steps fail with a clear error if `opencode` is not on `PATH`.
117
+ Generated `bash` and `vars` bodies run with `bash -euo pipefail`, so command failures stop the workflow instead of being masked by later successful commands. Captured `vars` values are exported for later `bash` steps. `agent` prompt heredocs are quoted by default and unquoted only when `expandPrompt: true` is set. `agent` steps invoke only `opencode run --format json -- <prompt>` with optional `--agent <agent>` before `--`, so dash-prefixed prompts are message content rather than OpenCode flags. Agent steps fail with a clear error if `opencode` is not on `PATH`.
100
118
 
101
119
  ## Development
102
120
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "flowsh-cli"
7
- version = "0.2.2"
7
+ version = "0.3.0"
8
8
  description = "Generate Bash harness scripts from workflow YAML files."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -3,7 +3,7 @@
3
3
  from flowsh_cli.models import Workflow, WorkflowParseError, parse_workflows
4
4
  from flowsh_cli.render import harness_path, render_harness
5
5
 
6
- __version__ = "0.2.2"
6
+ __version__ = "0.3.0"
7
7
 
8
8
  __all__ = [
9
9
  "Workflow",
@@ -11,7 +11,7 @@ from typing import Annotated
11
11
  import typer
12
12
 
13
13
  from flowsh_cli import __version__
14
- from flowsh_cli.models import Workflow, WorkflowParseError, parse_workflows
14
+ from flowsh_cli.models import Workflow, WorkflowParseError, parse_workflows, workflow_schema_yaml
15
15
  from flowsh_cli.render import harness_path, render_harness
16
16
 
17
17
  app = typer.Typer(
@@ -62,10 +62,20 @@ def generate(
62
62
  is_eager=True,
63
63
  ),
64
64
  ] = False,
65
+ schema: Annotated[
66
+ bool,
67
+ typer.Option(
68
+ "--schema",
69
+ callback=lambda value: print_schema(value),
70
+ help="Show the workflow YAML schema and exit.",
71
+ is_eager=True,
72
+ ),
73
+ ] = False,
65
74
  ) -> None:
66
75
  """Generate Bash harnesses from workflow YAML."""
67
76
 
68
77
  _ = version
78
+ _ = schema
69
79
 
70
80
  try:
71
81
  workflows = parse_workflows(workflow_yaml)
@@ -99,6 +109,14 @@ def print_version(value: bool) -> None:
99
109
  raise typer.Exit
100
110
 
101
111
 
112
+ def print_schema(value: bool) -> None:
113
+ if not value:
114
+ return
115
+
116
+ print(workflow_schema_yaml(), end="")
117
+ raise typer.Exit
118
+
119
+
102
120
  def write_harnesses(workflows: list[Workflow], *, dry_run: bool, force: bool) -> None:
103
121
  output_paths = [(workflow, harness_path(workflow)) for workflow in workflows]
104
122
 
@@ -67,6 +67,7 @@ class AgentStep(BaseStep):
67
67
  type: Literal["agent"]
68
68
  prompt: str
69
69
  agent: str | None = None
70
+ expandPrompt: bool = False
70
71
 
71
72
  @field_validator("prompt", "agent")
72
73
  @classmethod
@@ -175,6 +176,14 @@ def parse_workflows(path: Path) -> list[Workflow]:
175
176
  raise WorkflowParseError(format_validation_error(error)) from error
176
177
 
177
178
 
179
+ def workflow_schema_yaml() -> str:
180
+ return yaml.safe_dump(
181
+ WorkflowFile.model_json_schema(),
182
+ sort_keys=False,
183
+ allow_unicode=False,
184
+ )
185
+
186
+
178
187
  def format_validation_error(error: ValidationError) -> str:
179
188
  messages: list[str] = []
180
189
  for item in error.errors(include_url=False, include_input=False, include_context=False):
@@ -245,10 +245,11 @@ def render_step(index: int, step: Step, used_function_names: set[str] | None = N
245
245
  )
246
246
  elif isinstance(step, AgentStep):
247
247
  delimiter = heredoc_delimiter("PROMPT", step.prompt)
248
+ heredoc = f"<<{delimiter}" if step.expandPrompt else f"<<'{delimiter}'"
248
249
  lines.extend(
249
250
  [
250
251
  " local prompt",
251
- f" prompt=$(cat <<'{delimiter}'",
252
+ f" prompt=$(cat {heredoc}",
252
253
  *step.prompt.splitlines(),
253
254
  delimiter,
254
255
  " )",