Skip to content

Commit 05d8d54

Browse files
committed
feat: update SKILL.md with changes to block directives and execution behavior guidelines
1 parent 371c69d commit 05d8d54

4 files changed

Lines changed: 214 additions & 19 deletions

File tree

SKILL.md

Lines changed: 130 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,60 @@ pwd
114114

115115
Why it is bad: the `cd /srv/app` line becomes part of every block, including remote blocks.
116116

117-
## 4. Assume every block runs in a fresh shell
117+
## 4. Use block directives for timeout, retry, and exports
118+
119+
Block directives must appear immediately after the `# @LOCAL` or `# @REMOTE <host>` marker, before any command lines. They configure execution behavior for that specific block.
120+
121+
### Timeout Directive
122+
123+
`# @TIMEOUT <seconds>` - Abort the block if it exceeds the specified duration.
124+
125+
```bash
126+
# @LOCAL
127+
# @TIMEOUT 30
128+
sleep 60
129+
```
130+
131+
### Retry Directive
132+
133+
`# @RETRY <count>` - Retry the block up to N times on failure (0 means no retry).
134+
135+
```bash
136+
# @LOCAL
137+
# @RETRY 3
138+
curl -f https://api.example.com/health
139+
```
140+
141+
### Export Directive
142+
143+
`# @EXPORT NAME=source` - Capture a value from the block result and pass it to subsequent blocks as an environment variable.
144+
145+
Valid sources:
146+
147+
- `stdout` - The block's standard output
148+
- `stderr` - The block's standard error
149+
- `output` - Combined stdout and stderr
150+
- `exit_code` - The block's exit code (as string)
151+
152+
```bash
153+
# @LOCAL
154+
# @EXPORT BUILD_ID=stdout
155+
echo "build-$(date +%s)"
156+
157+
# @LOCAL
158+
echo "Building: $BUILD_ID"
159+
```
160+
161+
You can use multiple exports in a single block:
162+
163+
```bash
164+
# @LOCAL
165+
# @EXPORT STATUS_CODE=exit_code
166+
# @EXPORT RESPONSE=stdout
167+
curl -s -w "%{http_code}" -o response.txt https://api.example.com
168+
```
169+
170+
## 5. Assume every block runs in a fresh shell
118171

119172
Each block is isolated.
120173

@@ -158,7 +211,7 @@ pwd
158211

159212
Why it is bad: `artifact` and the working directory do not persist.
160213

161-
## 5. Use SHELLFLOW_LAST_OUTPUT for explicit handoff
214+
## 6. Use SHELLFLOW_LAST_OUTPUT for explicit handoff
162215

163216
Shellflow passes the previous block's combined output into the next block as `SHELLFLOW_LAST_OUTPUT`.
164217

@@ -197,7 +250,7 @@ print(payload["release"])
197250
PY
198251
```
199252

200-
## 6. Use SSH config host aliases, not ad-hoc targets
253+
## 7. Use SSH config host aliases, not ad-hoc targets
201254

202255
`# @REMOTE <ssh-host>` should point to a host that resolves through SSH config.
203256

@@ -210,7 +263,7 @@ Avoid assuming Shellflow accepts any arbitrary free-form destination unless it i
210263

211264
If a remote host is unknown, Shellflow fails before execution.
212265

213-
## 7. Keep blocks self-contained and fail-fast
266+
## 8. Keep blocks self-contained and fail-fast
214267

215268
Shellflow runs blocks in order and stops on the first failure.
216269

@@ -243,17 +296,69 @@ docker compose up -d
243296

244297
Why: the second block cannot rely on the first block's `cd`.
245298

299+
## 9. CLI Options and Output Modes
300+
301+
Shellflow provides several CLI options for different use cases:
302+
303+
### Basic Options
304+
305+
```bash
306+
shellflow run script.sh # Run a script
307+
shellflow run script.sh -v # Run with verbose output
308+
shellflow run script.sh --dry-run # Preview execution plan without running
309+
```
310+
311+
### Structured Output
312+
313+
```bash
314+
shellflow run script.sh --json # Single JSON report
315+
shellflow run script.sh --jsonl # Streaming JSON Lines events
316+
```
317+
318+
- `--json`: Outputs a single JSON object with the complete run report
319+
- `--jsonl`: Outputs one JSON object per event (run_started, block_started, block_finished, run_finished)
320+
321+
### Execution Control
322+
323+
```bash
324+
shellflow run script.sh --no-input # Non-interactive mode (stdin closed)
325+
shellflow run script.sh --ssh-config /path/to/config # Custom SSH config
326+
```
327+
328+
- `--no-input`: Closes stdin before running blocks; useful for automation
329+
- `--ssh-config`: Override the default SSH config path (`~/.ssh/config`)
330+
331+
### Audit Logging
332+
333+
```bash
334+
shellflow run script.sh --audit-log audit.jsonl --jsonl
335+
```
336+
337+
The `--audit-log` option writes redacted JSON Lines events to a file. Secret-like exports (containing TOKEN, SECRET, or PASSWORD in the name) are automatically redacted to `[REDACTED]`.
338+
339+
## 10. Exit Codes
340+
341+
Shellflow returns distinct exit codes for different failure types:
342+
343+
- `0`: Success
344+
- `1`: General execution failure
345+
- `2`: Parse failure (invalid script syntax)
346+
- `3`: SSH config failure (host not found)
347+
- `4`: Timeout failure (block exceeded timeout)
348+
246349
## Authoring Checklist
247350

248351
Before returning a Shellflow playbook, verify that:
249352

250353
- The script is valid bash without custom DSL syntax.
251-
- Only `# @LOCAL` and `# @REMOTE <host>` are used.
354+
- Only `# @LOCAL`, `# @REMOTE <host>`, and block directives (`# @TIMEOUT`, `# @RETRY`, `# @EXPORT`) are used.
355+
- Block directives appear immediately after the block marker, before any commands.
252356
- Anything before the first marker is safe to repeat for every block.
253357
- Every block can run independently in a fresh shell.
254-
- Cross-block data uses `SHELLFLOW_LAST_OUTPUT` explicitly.
358+
- Cross-block data uses `SHELLFLOW_LAST_OUTPUT` or `@EXPORT` explicitly.
255359
- Remote targets match the intended SSH host aliases.
256360
- Commands that should happen once are not accidentally placed in the shared prelude.
361+
- Export sources are valid (stdout, stderr, output, exit_code).
257362

258363
## Reference Example
259364

@@ -266,27 +371,36 @@ log() {
266371
}
267372

268373
# @LOCAL
374+
# @EXPORT BUILD_ID=stdout
269375
log "building artifact"
270-
artifact="$(mktemp /tmp/shellflow-release.XXXXXX)"
271-
printf 'release-%s\n' "$(date +%Y%m%d%H%M%S)" > "$artifact"
272-
echo "$artifact"
376+
build_id="build-$(date +%Y%m%d%H%M%S)"
377+
echo "$build_id"
378+
379+
# @LOCAL
380+
# @TIMEOUT 60
381+
# @RETRY 2
382+
log "deploying to staging"
383+
echo "Deploying $BUILD_ID to staging"
273384

274385
# @REMOTE staging
275-
cd /srv/app
276-
artifact_path="$SHELLFLOW_LAST_OUTPUT"
277-
log "receiving artifact path $artifact_path"
278-
test -n "$artifact_path"
279-
uname -a
386+
# @EXPORT DEPLOYED_HOST=stdout
387+
log "receiving deployment"
388+
hostname
280389

281390
# @LOCAL
282-
log "remote said: $SHELLFLOW_LAST_OUTPUT"
391+
log "deployed to: $DEPLOYED_HOST"
392+
log "build $BUILD_ID complete"
283393
```
284394

285395
## Common Mistakes
286396

287397
- Putting one-time commands before the first marker, then being surprised when they run for every block.
288398
- Expecting `cd`, `export`, or local shell variables from one block to exist in the next block.
289399
- Using an undefined remote host alias.
290-
- Printing extra debug output from a block whose output is consumed by the next block.
400+
- Placing block directives after commands instead of immediately after the marker.
401+
- Using invalid export sources (not stdout, stderr, output, or exit_code).
402+
- Forgetting that `@RETRY 0` means no retry attempts.
403+
- Using `@TIMEOUT` with values too small for normal operation.
404+
- Printing extra debug output from a block whose output is consumed by the next block via `@EXPORT`.
291405
- Forgetting to quote `"$SHELLFLOW_LAST_OUTPUT"`.
292406
- Treating Shellflow as a persistent session instead of sequential isolated shells.

playbooks/hello.sh

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,89 @@
11
#!/bin/bash
22
set -euo pipefail
33

4+
log() {
5+
printf '[hello] %s\n' "$*"
6+
}
7+
8+
log "Shellflow Feature Demo"
9+
10+
# Block 1: Local execution with timeout, retry, and export
11+
# Demonstrates: @TIMEOUT, @RETRY, @EXPORT, SHELLFLOW_LAST_OUTPUT
12+
# @LOCAL
13+
# @TIMEOUT 30
14+
# @RETRY 1
15+
# @EXPORT GREETING=stdout
16+
log "block 1: local with export"
17+
echo "Hello from Shellflow!"
18+
19+
# Block 2: Local execution reading exported variable
20+
# Demonstrates: reading exported env vars
21+
# @LOCAL
22+
log "block 2: using exported variable"
23+
echo "Received: $GREETING"
24+
25+
# Block 3: Local execution with SHELLFLOW_LAST_OUTPUT
26+
# Demonstrates: SHELLFLOW_LAST_OUTPUT handoff
27+
# @LOCAL
28+
log "block 3: passing data via SHELLFLOW_LAST_OUTPUT"
29+
echo "Data from block 2"
30+
31+
# Block 4: Local reading SHELLFLOW_LAST_OUTPUT
32+
# Demonstrates: SHELLFLOW_LAST_OUTPUT
33+
# @LOCAL
34+
log "block 4: reading SHELLFLOW_LAST_OUTPUT"
35+
echo "Previous output: $SHELLFLOW_LAST_OUTPUT"
36+
37+
# Block 5: Export exit_code
38+
# Demonstrates: @EXPORT with exit_code source
39+
# @LOCAL
40+
# @EXPORT COMMAND_STATUS=exit_code
41+
log "block 5: export exit_code"
42+
true
43+
44+
# Block 6: Using exported exit_code
45+
# @LOCAL
46+
log "block 6: previous exit code was $COMMAND_STATUS"
47+
48+
# Block 7: Export stderr
49+
# Demonstrates: @EXPORT with stderr source
50+
# @LOCAL
51+
# @EXPORT ERROR_OUTPUT=stderr
52+
log "block 7: exporting stderr"
53+
echo "normal output" >&2
54+
55+
# Block 8: Using exported stderr
56+
# @LOCAL
57+
log "block 8: stderr was: $ERROR_OUTPUT"
58+
59+
# Block 9: Export combined output
60+
# Demonstrates: @EXPORT with output source
61+
# @LOCAL
62+
# @EXPORT COMBINED=output
63+
log "block 9: exporting combined output"
64+
echo "stdout line"
65+
echo "stderr line" >&2
66+
67+
# Block 10: Using exported combined output
468
# @LOCAL
5-
echo "runs with the shared prelude"
69+
log "block 10: combined output:"
70+
echo "$COMBINED"
671

72+
# Block 11: Retry demonstration (will succeed on first try)
73+
# Demonstrates: @RETRY with successful execution
74+
# @LOCAL
75+
# @RETRY 3
76+
log "block 11: retry demo (succeeds)"
77+
echo "Retry successful!"
78+
79+
# Block 12: Remote execution demonstration (requires SSH config)
80+
# Demonstrates: @REMOTE for remote execution
781
# @REMOTE sui
882
uname -a
83+
84+
# Block 12: Final block
85+
# @LOCAL
86+
log "block 12: demo complete!"
87+
echo "All Shellflow features demonstrated."
88+
89+

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "shellflow"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
description = "A minimal shell script orchestrator with SSH support"
55
readme = "README.md"
66
license = "Apache-2.0"

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)