flowsh-cli 0.5.0__tar.gz → 0.6.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.
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/PKG-INFO +1 -1
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/pyproject.toml +1 -1
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/src/flowsh_cli/__init__.py +1 -1
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/src/flowsh_cli/models.py +27 -1
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/src/flowsh_cli/render.py +94 -6
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/README.md +0 -0
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/src/flowsh_cli/__main__.py +0 -0
- {flowsh_cli-0.5.0 → flowsh_cli-0.6.0}/src/flowsh_cli/cli.py +0 -0
|
@@ -154,7 +154,33 @@ class VarsStep(BaseStep):
|
|
|
154
154
|
return value
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
class ForStep(BaseStep):
|
|
158
|
+
type: Literal["for"]
|
|
159
|
+
in_: str = Field(..., validation_alias="in")
|
|
160
|
+
item: str = Field(...)
|
|
161
|
+
steps: list[Step] = Field(min_length=1)
|
|
162
|
+
|
|
163
|
+
@field_validator("in_", "item")
|
|
164
|
+
@classmethod
|
|
165
|
+
def validate_var_name(cls, value: str) -> str:
|
|
166
|
+
if not re.fullmatch(r"[A-Z_][A-Z0-9_]*", value):
|
|
167
|
+
raise ValueError("must match ^[A-Z_][A-Z0-9_]*$")
|
|
168
|
+
return value
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ParallelStep(BaseStep):
|
|
172
|
+
type: Literal["parallel"]
|
|
173
|
+
steps: list[Step] = Field(min_length=1)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
Step = Annotated[
|
|
177
|
+
VarsStep | BashStep | AgentStep | ForStep | ParallelStep,
|
|
178
|
+
Field(discriminator="type"),
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
ForStep.model_rebuild()
|
|
183
|
+
ParallelStep.model_rebuild()
|
|
158
184
|
|
|
159
185
|
|
|
160
186
|
class WorkflowParam(StrictModel):
|
|
@@ -3,7 +3,16 @@ from __future__ import annotations
|
|
|
3
3
|
import re
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from flowsh_cli.models import
|
|
6
|
+
from flowsh_cli.models import (
|
|
7
|
+
AgentStep,
|
|
8
|
+
BashStep,
|
|
9
|
+
ForStep,
|
|
10
|
+
ParallelStep,
|
|
11
|
+
Step,
|
|
12
|
+
VarsStep,
|
|
13
|
+
Workflow,
|
|
14
|
+
WorkflowParam,
|
|
15
|
+
)
|
|
7
16
|
|
|
8
17
|
|
|
9
18
|
def harness_path(workflow: Workflow) -> Path:
|
|
@@ -221,8 +230,64 @@ def render_harness(workflow: Workflow) -> str:
|
|
|
221
230
|
def render_step(index: int, step: Step, used_function_names: set[str] | None = None) -> list[str]:
|
|
222
231
|
function_name = step_function_name(index, step.name, used_function_names)
|
|
223
232
|
title = step.name or default_step_title(index, step)
|
|
224
|
-
lines = [section(f"Step {index} ({step.type}): {title}"), f"{function_name}() {{"]
|
|
225
233
|
|
|
234
|
+
prefix_lines: list[str] = []
|
|
235
|
+
|
|
236
|
+
if isinstance(step, ForStep):
|
|
237
|
+
inner_fns: list[str] = []
|
|
238
|
+
for i, inner_step in enumerate(step.steps, start=1):
|
|
239
|
+
inner_fn = _for_inner_function_name(index, i, inner_step, used_function_names)
|
|
240
|
+
inner_fns.append(inner_fn)
|
|
241
|
+
inner_title = inner_step.name or default_step_title(i, inner_step)
|
|
242
|
+
prefix_lines.append(section(f"For-inner step ({inner_step.type}): {inner_title}"))
|
|
243
|
+
prefix_lines.append(f"{inner_fn}() {{")
|
|
244
|
+
prefix_lines.extend(_render_step_body(inner_step))
|
|
245
|
+
prefix_lines.append("}")
|
|
246
|
+
prefix_lines.append("")
|
|
247
|
+
|
|
248
|
+
body_lines = [
|
|
249
|
+
f" while IFS= read -r {step.item}; do",
|
|
250
|
+
f" export {step.item}",
|
|
251
|
+
*[f" run_step {fn}" for fn in inner_fns],
|
|
252
|
+
f' done <<< "${{{step.in_}}}"',
|
|
253
|
+
]
|
|
254
|
+
elif isinstance(step, ParallelStep):
|
|
255
|
+
child_fns: list[str] = []
|
|
256
|
+
for i, child in enumerate(step.steps, start=1):
|
|
257
|
+
child_title = child.name or default_step_title(i, child)
|
|
258
|
+
child_fn = step_function_name(i, child.name, used_function_names)
|
|
259
|
+
child_fns.append(child_fn)
|
|
260
|
+
prefix_lines.append(section(f"Parallel child {i} ({child.type}): {child_title}"))
|
|
261
|
+
prefix_lines.append(f"{child_fn}() {{")
|
|
262
|
+
prefix_lines.extend(_render_step_body(child))
|
|
263
|
+
prefix_lines.append("}")
|
|
264
|
+
prefix_lines.append("")
|
|
265
|
+
|
|
266
|
+
body_lines = [" local status=0"]
|
|
267
|
+
for child_fn in child_fns:
|
|
268
|
+
body_lines.append(f' "{child_fn}" &')
|
|
269
|
+
body_lines.append(f" local pid_{child_fn}=$!")
|
|
270
|
+
for child_fn in child_fns:
|
|
271
|
+
body_lines.append(f' wait "$pid_{child_fn}" || status=$?')
|
|
272
|
+
body_lines.append(' return "$status"')
|
|
273
|
+
else:
|
|
274
|
+
body_lines = _render_step_body(step)
|
|
275
|
+
|
|
276
|
+
runner = "run_stateful_step" if isinstance(step, (VarsStep, ForStep)) else "run_step"
|
|
277
|
+
outer_lines = [
|
|
278
|
+
section(f"Step {index} ({step.type}): {title}"),
|
|
279
|
+
f"{function_name}() {{",
|
|
280
|
+
*body_lines,
|
|
281
|
+
"}",
|
|
282
|
+
f"{runner} {function_name}",
|
|
283
|
+
"",
|
|
284
|
+
]
|
|
285
|
+
return prefix_lines + outer_lines
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _render_step_body(step: Step) -> list[str]:
|
|
289
|
+
"""Return indented body lines for a step function (no wrapper, no run_step call)."""
|
|
290
|
+
lines: list[str] = []
|
|
226
291
|
if isinstance(step, VarsStep):
|
|
227
292
|
lines.append(" local status=0")
|
|
228
293
|
for name, command in step.values.items():
|
|
@@ -269,7 +334,6 @@ def render_step(index: int, step: Step, used_function_names: set[str] | None = N
|
|
|
269
334
|
for var in seen:
|
|
270
335
|
lines.append(f' _p=\'${{{var}}}\'; prompt="${{prompt//"$_p"/"${var}"}}"')
|
|
271
336
|
lines.append(f' _p=\'${var}\'; prompt="${{prompt//"$_p"/"${var}"}}"')
|
|
272
|
-
|
|
273
337
|
lines.append(f" local agent={bash_quote(step.agent or '')}")
|
|
274
338
|
lines.append(f" local model={bash_quote(step.model or '')}")
|
|
275
339
|
lines.append(f" local command={bash_quote(step.command or '')}")
|
|
@@ -278,14 +342,34 @@ def render_step(index: int, step: Step, used_function_names: set[str] | None = N
|
|
|
278
342
|
lines.append(
|
|
279
343
|
' run_agent "$prompt" "$agent" "$model" "$command" "$dangerously_skip_permissions"'
|
|
280
344
|
)
|
|
345
|
+
elif isinstance(step, ForStep):
|
|
346
|
+
raise AssertionError("nested for steps are not supported")
|
|
281
347
|
else:
|
|
282
348
|
raise AssertionError(f"Unsupported step type: {step}")
|
|
283
|
-
|
|
284
|
-
runner = "run_stateful_step" if isinstance(step, VarsStep) else "run_step"
|
|
285
|
-
lines.extend(["}", f"{runner} {function_name}", ""])
|
|
286
349
|
return lines
|
|
287
350
|
|
|
288
351
|
|
|
352
|
+
def _for_inner_function_name(
|
|
353
|
+
outer_index: int,
|
|
354
|
+
inner_index: int,
|
|
355
|
+
step: Step,
|
|
356
|
+
used_function_names: set[str] | None,
|
|
357
|
+
) -> str:
|
|
358
|
+
source = step.name or f"step_{inner_index}"
|
|
359
|
+
slug = re.sub(r"[^A-Za-z0-9_]+", "_", source).strip("_").lower()
|
|
360
|
+
if not slug or slug[0].isdigit():
|
|
361
|
+
slug = f"step_{slug}" if slug else f"step_{inner_index}"
|
|
362
|
+
base = f"for_{outer_index}_{slug}"
|
|
363
|
+
fn = base
|
|
364
|
+
suffix = 2
|
|
365
|
+
while used_function_names is not None and fn in used_function_names:
|
|
366
|
+
fn = f"{base}_{suffix}"
|
|
367
|
+
suffix += 1
|
|
368
|
+
if used_function_names is not None:
|
|
369
|
+
used_function_names.add(fn)
|
|
370
|
+
return fn
|
|
371
|
+
|
|
372
|
+
|
|
289
373
|
def default_step_title(index: int, step: Step) -> str:
|
|
290
374
|
if isinstance(step, VarsStep):
|
|
291
375
|
return ", ".join(step.values.keys())
|
|
@@ -293,6 +377,10 @@ def default_step_title(index: int, step: Step) -> str:
|
|
|
293
377
|
return truncate_one_line(step.run)
|
|
294
378
|
if isinstance(step, AgentStep):
|
|
295
379
|
return truncate_one_line(step.prompt)
|
|
380
|
+
if isinstance(step, ForStep):
|
|
381
|
+
return f"for {step.item} in {step.in_}"
|
|
382
|
+
if isinstance(step, ParallelStep):
|
|
383
|
+
return f"parallel ({len(step.steps)} steps)"
|
|
296
384
|
return f"step {index}"
|
|
297
385
|
|
|
298
386
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|