1pub mod error;
20
21mod context;
22pub mod course;
23mod minimal;
24mod schemas;
25mod showcase_advanced;
26mod showcase_fetch;
27mod showcase_infra;
28mod showcase_patterns;
29
30pub use context::*;
31pub use minimal::*;
32pub use schemas::*;
33pub use showcase_advanced::*;
34pub use showcase_fetch::*;
35pub use showcase_infra::*;
36pub use showcase_patterns::*;
37
38pub struct WorkflowTemplate {
40 pub filename: &'static str,
42 pub tier_dir: &'static str,
44 pub content: &'static str,
46}
47
48pub struct ContextFile {
50 pub filename: &'static str,
51 pub dir: &'static str,
52 pub content: &'static str,
53}
54
55pub fn get_all_workflows() -> Vec<WorkflowTemplate> {
57 let mut all = minimal::get_minimal_workflows();
58 all.extend(showcase_patterns::get_showcase_workflows());
59 all.extend(showcase_advanced::get_showcase_advanced_workflows());
60 all.extend(showcase_infra::get_showcase_infra_workflows());
61 all.extend(showcase_fetch::get_showcase_fetch_workflows());
62 all
63}
64
65pub fn get_all_context_files() -> Vec<ContextFile> {
66 context::get_context_files()
67}
68
69pub fn get_all_schemas() -> Vec<ContextFile> {
70 schemas::get_schema_files()
71}
72
73pub const WORKFLOWS_README: &str = r#"# Nika Workflows
75
76> 5 minimal starter workflows + interactive course with 12 levels.
77
78## Quick Start
79
80```bash
81# 1. Run immediately (no API key needed)
82nika run workflows/minimal/01-exec.nika.yaml
83nika run workflows/minimal/02-fetch.nika.yaml
84
85# 2. Setup LLM provider, then run LLM workflows
86nika provider set anthropic # or: openai, mistral, groq, deepseek, gemini
87nika run workflows/minimal/03-infer.nika.yaml
88nika run workflows/minimal/04-invoke.nika.yaml
89nika run workflows/minimal/05-agent.nika.yaml
90```
91
92## Minimal Starters (5 workflows)
93
94| # | File | Verb | Prerequisites |
95|---|------|------|---------------|
96| 01 | `01-exec.nika.yaml` | `exec:` | None |
97| 02 | `02-fetch.nika.yaml` | `fetch:` | None |
98| 03 | `03-infer.nika.yaml` | `infer:` | LLM provider |
99| 04 | `04-invoke.nika.yaml` | `invoke:` | None (builtins) |
100| 05 | `05-agent.nika.yaml` | `agent:` | LLM provider |
101
102## Interactive Course (12 levels)
103
104Start the course to learn Nika from zero to production:
105
106```bash
107nika course next
108```
109
110| # | Level | Exercises | Theme |
111|---|-------|-----------|-------|
112| 01 | Jailbreak | 5 | Break free — exec: basics |
113| 02 | Hot Wire | 4 | Network — fetch: HTTP |
114| 03 | Fork Bomb | 4 | Multiply — DAG patterns |
115| 04 | Root Access | 3 | Unlock LLM — infer: |
116| 05 | Shapeshifter | 3 | Transform — with: bindings |
117| 06 | Pay-Per-Dream | 3 | Structured output |
118| 07 | Swiss Knife | 3 | Builtin tools — invoke: |
119| 08 | Gone Rogue | 3 | Autonomous — agent: |
120| 09 | Data Heist | 4 | Extraction — fetch: extract |
121| 10 | Open Protocol | 3 | MCP integration |
122| 11 | Pixel Pirate | 4 | Media pipeline |
123| 12 | SuperNovae | 5 | Boss — full orchestration |
124
125## Provider Setup
126
127```bash
128nika provider list # Check available providers
129nika provider set anthropic # Claude (recommended)
130nika provider set openai # GPT-4
131nika provider set mistral # Mistral Large
132nika provider set groq # Groq (fast, free tier)
133nika provider set deepseek # DeepSeek
134nika provider set gemini # Google Gemini
135```
136
137## Learn More
138
139- [Nika Documentation](https://github.com/supernovae-st/nika)
140- [NovaNet](https://github.com/supernovae-st/novanet)
141"#;
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_all_workflows_count() {
149 let workflows = get_all_workflows();
150 assert_eq!(
151 workflows.len(),
152 65,
153 "Should have 5 minimal + 15 patterns + 15 advanced + 15 infra + 15 fetch"
154 );
155 }
156
157 #[test]
158 fn test_workflow_filenames_unique() {
159 let workflows = get_all_workflows();
160 let mut f: Vec<&str> = workflows.iter().map(|w| w.filename).collect();
161 let n = f.len();
162 f.sort();
163 f.dedup();
164 assert_eq!(f.len(), n);
165 }
166
167 #[test]
168 fn test_workflows_have_schema() {
169 let workflows = get_all_workflows();
170 for w in &workflows {
171 assert!(
172 w.content.contains("schema: \"nika/workflow@0.12\""),
173 "Workflow {} should have schema declaration",
174 w.filename
175 );
176 }
177 }
178
179 #[test]
180 fn test_workflows_have_workflow_name() {
181 let workflows = get_all_workflows();
182 for w in &workflows {
183 assert!(
184 w.content.contains("workflow:"),
185 "Workflow {} should have workflow: declaration",
186 w.filename
187 );
188 }
189 }
190
191 #[test]
192 fn test_workflows_have_tasks() {
193 for w in get_all_workflows() {
194 assert!(w.content.contains("tasks:"));
195 }
196 }
197
198 #[test]
199 fn test_context_files_exist() {
200 assert!(get_all_context_files().len() >= 2);
201 }
202
203 #[test]
204 fn test_schema_files_valid_json() {
205 let files = get_all_schemas();
206 for f in &files {
207 assert!(
208 f.filename.ends_with(".schema.json"),
209 "Schema {} should end with .schema.json",
210 f.filename
211 );
212 let parsed: Result<serde_json::Value, _> = serde_json::from_str(f.content);
213 assert!(
214 parsed.is_ok(),
215 "Schema {} should be valid JSON: {:?}",
216 f.filename,
217 parsed.err()
218 );
219 }
220 }
221
222 #[test]
223 fn test_readme_exists() {
224 assert!(!WORKFLOWS_README.is_empty(), "README should not be empty");
225 assert!(
226 WORKFLOWS_README.contains("Nika Workflows"),
227 "README should have title"
228 );
229 assert!(
230 WORKFLOWS_README.contains("Quick Start"),
231 "README should have Quick Start section"
232 );
233 }
234
235 #[test]
236 fn test_workflows_valid_yaml() {
237 let workflows = get_all_workflows();
238 for w in &workflows {
239 if w.content.contains("{{PROVIDER}}") || w.content.contains("{{MODEL}}") {
241 continue;
242 }
243 let parsed: Result<serde_json::Value, _> = serde_saphyr::from_str(w.content);
244 assert!(
245 parsed.is_ok(),
246 "Workflow {} should be valid YAML: {:?}",
247 w.filename,
248 parsed.err()
249 );
250 }
251 }
252
253 #[test]
254 fn test_course_levels_exist() {
255 assert_eq!(
256 course::levels::LEVELS.len(),
257 12,
258 "Course should have 12 levels"
259 );
260 }
261
262 #[test]
263 fn test_course_total_exercises() {
264 assert_eq!(
266 course::levels::total_exercises(),
267 44,
268 "Course should have 44 total exercises"
269 );
270 }
271}