Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,75 @@ steps:
command: |
jq -n --arg text "$TEXT" '{"result": $text}'
```

## Real-world example: Daily standup with Jira and LLM

A common pattern: fetch data via shell CLI, then feed it to an LLM tool for summarization. We include a complete, documented example:

**File**: [`examples/daily-standup.lobster`](examples/daily-standup.lobster)

```yaml
name: daily-standup
description: |
Daily standup pipeline: fetch tickets from Jira, summarize with LLM.
Demonstrates mixing shell commands (jira, jq) with openclaw.invoke tool calls.

args:
team:
default: "CLAW"
description: "Jira team/project key"
project:
default: "E-commerce"
description: "Project name for filtering"
limit:
default: "30"
description: "Maximum number of tickets to fetch"
llm_prompt:
default: "Summarize the top 10 most urgent tickets for the daily standup. Output a concise bullet list with ticket IDs and key points."
description: "Prompt sent to the LLM"

steps:
- id: list-tickets
command: >
jira issues search "project=${project} AND status=Todo" --json |
jq -s '[.[] | {id: .key, title: .fields.summary, status: .fields.status.name, priority: .fields.priority.name, assignee: (.fields.assignee.displayName // "unassigned")] | .[0:env.LOBSTER_ARG_limit | tonumber]' 2>/dev/null
env:
LOBSTER_ARG_limit: "${limit}"

- id: summarize
command: >
openclaw.invoke --tool llm-task --action json --args-json '{"prompt": "${llm_prompt}"}'
stdin: $list-tickets.stdout

- id: output
command: >
echo "=== Standup Summary ===" && echo && cat
stdin: $summarize.stdout
```

Key takeaways:

- Use `stdin: $stepId.stdout` to pass data without temp files
- Access numeric/complex args via safe env vars: `env.LOBSTER_ARG_<NAME>`
- Call `openclaw.invoke` directly from any shell step
- Works both standalone and from OpenClaw cron

### Running standalone

```bash
export OPENCLAW_URL=http://127.0.0.1:18789
lobster run --file examples/daily-standup.lobster --args-json '{"project":"E-commerce","limit":20}'
```

### From OpenClaw cron

```json
{
"action": "run",
"pipeline": "examples/daily-standup.lobster",
"args": { "project": "E-commerce", "limit": 20 },
"timeoutMs": 60000
}
```

This example addresses the confusion reported in [#26](https://github.com/openclaw/lobster/issues/26) and shows a clean, maintainable way to compose shell and tool steps.
48 changes: 48 additions & 0 deletions examples/daily-standup.lobster
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: daily-standup
description: |
Daily standup pipeline: fetch tickets from Jira, summarize with LLM.
Demonstrates mixing shell commands (jira, jq) with openclaw.invoke tool calls.

args:
team:
default: "CLAW"
description: "Jira team/project key"
project:
default: "E-commerce"
description: "Project name for filtering"
limit:
default: "30"
description: "Maximum number of tickets to fetch"
llm_prompt:
default: "Summarize the top 10 most urgent tickets for the daily standup. Output a concise bullet list with ticket IDs and key points."
description: "Prompt sent to the LLM"

steps:
- id: list-tickets
command: >
jira issues search "project=${project} AND status=Todo" --json |
jq -s '[.[] | {id: .key, title: .fields.summary, status: .fields.status.name, priority: .fields.priority.name, assignee: (.fields.assignee.displayName // "unassigned")] | .[0:env.LOBSTER_ARG_limit | tonumber]' 2>/dev/null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fix malformed jq filter in list-tickets step

The jq program in this step has a bracket mismatch (] where the object should close with }), so jq fails to compile and the workflow cannot produce ticket JSON. Because stderr is redirected to /dev/null, this failure is also hard to diagnose for users trying the example, making the new documented pipeline non-runnable as written.

Useful? React with 👍 / 👎.

env:
# Ensure numeric limit is passed safely via env var
LOBSTER_ARG_limit: "${limit}"

- id: summarize
command: >
openclaw.invoke --tool llm-task --action json --args-json '{"prompt": "${llm_prompt}"}'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Escape llm_prompt before embedding it in --args-json

This command injects ${llm_prompt} directly into a JSON literal inside shell quotes, but README already documents this substitution as raw text replacement. Any prompt containing quotes (or apostrophes) will produce invalid JSON or broken shell quoting, causing openclaw.invoke to fail for common real inputs.

Useful? React with 👍 / 👎.

stdin: $list-tickets.stdout
# Pass the list of tickets as JSON via stdin to the LLM task

- id: output
command: >
echo "=== Standup Summary ===" && echo && cat
stdin: $summarize.stdout

# Notes:
# - This workflow expects `jira` CLI to be installed and configured.
# - OPENCLAW_URL and optionally OPENCLAW_TOKEN must be set in the environment.
# - The `openclaw.invoke` shim is installed with Lobster and calls the llm-task tool.
# - Data is passed between steps using stdin, avoiding temporary files.
# - To run manually:
# lobster run --file examples/daily-standup.lobster --args-json '{"project":"E-commerce","limit":20}'
# - To run from OpenClaw cron:
# {action:"run", pipeline:"examples/daily-standup.lobster", args:{"project":"E-commerce"}, timeoutMs:60000}