use super::WorkflowTemplate;
pub const MINIMAL_EXEC: &str = r##"# ═══════════════════════════════════════════════════════════════════
# exec: — Run shell commands
# ═══════════════════════════════════════════════════════════════════
#
# The simplest Nika verb. No API key, no provider — just your shell.
#
# Run: nika run workflows/01-exec.nika.yaml
schema: "nika/workflow@0.12"
workflow: exec-basics
description: "Shell commands with exec:"
tasks:
- id: hello
exec:
command: echo "Hello from Nika!"
- id: system_info
exec:
command: uname -a
timeout: 10
- id: count_files
depends_on: [hello]
exec:
command: ls -1 | wc -l
shell: true
"##;
pub const MINIMAL_FETCH: &str = r##"# ═══════════════════════════════════════════════════════════════════
# fetch: — HTTP requests
# ═══════════════════════════════════════════════════════════════════
#
# Make HTTP requests. No API key needed for public endpoints.
#
# Run: nika run workflows/02-fetch.nika.yaml
schema: "nika/workflow@0.12"
workflow: fetch-basics
description: "HTTP requests with fetch:"
tasks:
- id: get_ip
fetch:
url: "https://httpbin.org/ip"
method: GET
- id: post_data
fetch:
url: "https://httpbin.org/post"
method: POST
headers:
Content-Type: "application/json"
body: '{"message": "Hello from Nika"}'
- id: show_result
depends_on: [get_ip]
with:
ip_data: $get_ip
exec:
command: echo "Your IP data — {{with.ip_data}}"
"##;
pub const MINIMAL_INFER: &str = r##"# ═══════════════════════════════════════════════════════════════════
# infer: — LLM generation
# ═══════════════════════════════════════════════════════════════════
#
# Send prompts to an LLM. Requires a provider API key.
#
# Setup: nika provider set {{PROVIDER}}
# Run: nika run workflows/03-infer.nika.yaml
schema: "nika/workflow@0.12"
workflow: infer-basics
description: "LLM prompts with infer:"
tasks:
- id: haiku
infer:
model: "{{MODEL}}"
prompt: "Write a haiku about open source software."
temperature: 0.8
max_tokens: 100
- id: explain
infer:
model: "{{MODEL}}"
system: "You are a concise technical writer."
prompt: "Explain what a DAG is in 2 sentences."
temperature: 0.3
max_tokens: 200
- id: combine
depends_on: [haiku, explain]
with:
poem: $haiku
definition: $explain
infer:
model: "{{MODEL}}"
prompt: |
Combine these into a short blog post intro:
HAIKU:
{{with.poem}}
DAG DEFINITION:
{{with.definition}}
temperature: 0.5
max_tokens: 400
"##;
pub const MINIMAL_INVOKE: &str = r##"# ═══════════════════════════════════════════════════════════════════
# invoke: — MCP tool calls
# ═══════════════════════════════════════════════════════════════════
#
# Call builtin tools or external MCP servers.
# Builtin tools (nika:*) need no setup.
#
# Run: nika run workflows/04-invoke.nika.yaml
schema: "nika/workflow@0.12"
workflow: invoke-basics
description: "Tool calls with invoke:"
tasks:
- id: log_start
invoke:
tool: "nika:log"
params:
message: "Workflow started"
level: info
- id: emit_data
invoke:
tool: "nika:emit"
params:
key: "greeting"
value: "Hello from invoke!"
- id: assert_check
depends_on: [emit_data]
invoke:
tool: "nika:assert"
params:
condition: true
message: "Emit succeeded"
"##;
pub const MINIMAL_AGENT: &str = r##"# ═══════════════════════════════════════════════════════════════════
# agent: — Multi-turn LLM with tools
# ═══════════════════════════════════════════════════════════════════
#
# An autonomous agent that can use tools in a loop.
# Requires a provider API key.
#
# Setup: nika provider set {{PROVIDER}}
# Run: nika run workflows/05-agent.nika.yaml
schema: "nika/workflow@0.12"
workflow: agent-basics
description: "Multi-turn agent with tools"
tasks:
- id: scout
agent:
model: "{{MODEL}}"
system: |
You are a helpful file scout. List the files in the current
directory and describe what you find. Be concise.
prompt: "What files are in this project?"
tools:
- "nika:glob"
- "nika:read"
max_turns: 5
stop_condition: "answer_found"
"##;
pub fn get_minimal_workflows() -> Vec<WorkflowTemplate> {
vec![
WorkflowTemplate {
filename: "01-exec.nika.yaml",
tier_dir: "minimal",
content: MINIMAL_EXEC,
},
WorkflowTemplate {
filename: "02-fetch.nika.yaml",
tier_dir: "minimal",
content: MINIMAL_FETCH,
},
WorkflowTemplate {
filename: "03-infer.nika.yaml",
tier_dir: "minimal",
content: MINIMAL_INFER,
},
WorkflowTemplate {
filename: "04-invoke.nika.yaml",
tier_dir: "minimal",
content: MINIMAL_INVOKE,
},
WorkflowTemplate {
filename: "05-agent.nika.yaml",
tier_dir: "minimal",
content: MINIMAL_AGENT,
},
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_minimal_workflow_count() {
let workflows = get_minimal_workflows();
assert_eq!(
workflows.len(),
5,
"Should have exactly 5 minimal workflows"
);
}
#[test]
fn test_minimal_filenames_unique() {
let workflows = get_minimal_workflows();
let mut names: Vec<&str> = workflows.iter().map(|w| w.filename).collect();
let len = names.len();
names.sort();
names.dedup();
assert_eq!(names.len(), len, "All filenames must be unique");
}
#[test]
fn test_minimal_all_have_schema() {
let workflows = get_minimal_workflows();
for w in &workflows {
assert!(
w.content.contains("schema: \"nika/workflow@0.12\""),
"Workflow {} must declare schema",
w.filename
);
}
}
#[test]
fn test_minimal_all_have_tasks() {
let workflows = get_minimal_workflows();
for w in &workflows {
assert!(
w.content.contains("tasks:"),
"Workflow {} must have tasks section",
w.filename
);
}
}
#[test]
fn test_minimal_all_nika_yaml_extension() {
let workflows = get_minimal_workflows();
for w in &workflows {
assert!(
w.filename.ends_with(".nika.yaml"),
"Workflow {} must end with .nika.yaml",
w.filename
);
}
}
#[test]
fn test_minimal_verbs_covered() {
let workflows = get_minimal_workflows();
let all_content: String = workflows.iter().map(|w| w.content).collect();
assert!(all_content.contains("exec:"), "Must cover exec: verb");
assert!(all_content.contains("fetch:"), "Must cover fetch: verb");
assert!(all_content.contains("infer:"), "Must cover infer: verb");
assert!(all_content.contains("invoke:"), "Must cover invoke: verb");
assert!(all_content.contains("agent:"), "Must cover agent: verb");
}
#[test]
fn test_minimal_valid_yaml() {
let workflows = get_minimal_workflows();
for w in &workflows {
if w.content.contains("{{PROVIDER}}") || w.content.contains("{{MODEL}}") {
continue;
}
let parsed: Result<serde_json::Value, _> = serde_saphyr::from_str(w.content);
assert!(
parsed.is_ok(),
"Workflow {} must be valid YAML: {:?}",
w.filename,
parsed.err()
);
}
}
}