pyrtan / sourcev0.19.0 · main

pyrtan / tests / conformance / vectors.json

452,695 bytes · last changed 2026-10-28 in Regenerate the conformance vectors over main's /health request and the new trees

1{
2 "clock": "2026-10-25T12:00:00+00:00",
3 "drafts": [
4 {
5 "expect": {
6 "branch": "{ when = { between = [4, 44] }, then = \"sky-4\" },",
7 "node": "sky",
8 "observed": 4,
9 "on": "code",
10 "reason": "the value falls in the hole between 3 and 45, which the ranges around it leave open",
11 "then": "sky-4",
12 "tree": "weather",
13 "when": {
14 "between": [
15 4,
16 44
17 ]
18 }
19 },
20 "gap": {
21 "node": "sky",
22 "observed": 4,
23 "on": "code"
24 },
25 "tree": "weather"
26 },
27 {
28 "expect": {
29 "branch": "{ when = { eq = 100 }, then = \"sky-100\" },",
30 "node": "sky",
31 "observed": 100,
32 "on": "code",
33 "reason": "the value itself",
34 "then": "sky-100",
35 "tree": "weather",
36 "when": {
37 "eq": 100
38 }
39 },
40 "gap": {
41 "node": "sky",
42 "observed": 100,
43 "on": "code"
44 },
45 "tree": "weather"
46 },
47 {
48 "expect": null,
49 "gap": {
50 "node": "sky",
51 "observed": 61,
52 "on": "code"
53 },
54 "tree": "weather"
55 },
56 {
57 "expect": {
58 "branch": "{ when = { exists = false }, then = \"sky-missing\" },",
59 "node": "sky",
60 "observed": null,
61 "on": "code",
62 "reason": "the value was missing",
63 "then": "sky-missing",
64 "tree": "weather",
65 "when": {
66 "exists": false
67 }
68 },
69 "gap": {
70 "node": "sky",
71 "observed": null,
72 "on": "code"
73 },
74 "tree": "weather"
75 },
76 {
77 "expect": {
78 "branch": "{ when = { type = \"dict\" }, then = \"sky-dict\" },",
79 "node": "sky",
80 "observed": "<dict>",
81 "on": "code",
82 "reason": "a report carries a long or structured value as its type",
83 "then": "sky-dict",
84 "tree": "weather",
85 "when": {
86 "type": "dict"
87 }
88 },
89 "gap": {
90 "node": "sky",
91 "observed": "<dict>",
92 "on": "code"
93 },
94 "tree": "weather"
95 },
96 {
97 "expect": {
98 "branch": "{ when = { eq = \"Sunny Spells\" }, then = \"sky-sunny-spells\" },",
99 "node": "sky",
100 "observed": "Sunny Spells",
101 "on": "code",
102 "reason": "the value itself",
103 "then": "sky-sunny-spells",
104 "tree": "weather",
105 "when": {
106 "eq": "Sunny Spells"
107 }
108 },
109 "gap": {
110 "node": "sky",
111 "observed": "Sunny Spells",
112 "on": "code"
113 },
114 "tree": "weather"
115 },
116 {
117 "expect": null,
118 "gap": {
119 "node": "geocode",
120 "observed": 1,
121 "on": "x"
122 },
123 "tree": "weather"
124 },
125 {
126 "expect": null,
127 "gap": {
128 "node": "status",
129 "observed": 700
130 },
131 "tree": "site-status"
132 },
133 {
134 "expect": {
135 "branch": "{ when = { eq = true }, then = \"status-true\" },",
136 "node": "status",
137 "observed": true,
138 "on": "resp.status",
139 "reason": "the value itself",
140 "then": "status-true",
141 "tree": "site-status",
142 "when": {
143 "eq": true
144 }
145 },
146 "gap": {
147 "node": "status",
148 "observed": true
149 },
150 "tree": "site-status"
151 }
152 ],
153 "fixture": "tests/fixtures/examples.json",
154 "hub": [
155 {
156 "body": null,
157 "expect": {
158 "name": "this browser",
159 "trees": [
160 {
161 "description": "A random piece of advice, from the Advice Slip API.",
162 "hosts": [
163 "api.adviceslip.com"
164 ],
165 "inputs": {},
166 "language": {},
167 "license": "MIT",
168 "name": "advice",
169 "namespace": "pyrtan",
170 "sources": [
171 "http"
172 ],
173 "tags": [
174 "fun",
175 "read-only"
176 ],
177 "triggers": [
178 "give me [some] advice",
179 "[any] advice"
180 ],
181 "version": 1
182 },
183 {
184 "description": "Current air quality for a place: the European AQI, its band, and PM2.5 and PM10, from Open-Meteo.",
185 "hosts": [
186 "geocoding-api.open-meteo.com",
187 "air-quality-api.open-meteo.com"
188 ],
189 "inputs": {
190 "place": {
191 "description": "A town or city name",
192 "required": true,
193 "type": "str"
194 }
195 },
196 "language": {},
197 "license": "MIT",
198 "name": "air-quality",
199 "namespace": "pyrtan",
200 "sources": [
201 "http"
202 ],
203 "tags": [
204 "weather",
205 "health",
206 "read-only"
207 ],
208 "triggers": [
209 "[what's the] air quality (in|for) {place}",
210 "how is the air in {place}",
211 "(aqi|pollution) in {place}"
212 ],
213 "version": 1
214 },
215 {
216 "description": "NASA's Astronomy Picture of the Day: its title, and the picture or video.",
217 "hosts": [
218 "api.nasa.gov"
219 ],
220 "inputs": {},
221 "language": {},
222 "license": "MIT",
223 "name": "apod",
224 "namespace": "pyrtan",
225 "sources": [
226 "http"
227 ],
228 "tags": [
229 "space",
230 "science",
231 "read-only"
232 ],
233 "triggers": [
234 "astronomy picture of the day",
235 "[nasa] (apod|space picture) [of the day|today]"
236 ],
237 "version": 1
238 },
239 {
240 "description": "A book from Open Library: the best match for a title or an author, who wrote it, and when it first came out.",
241 "hosts": [
242 "openlibrary.org"
243 ],
244 "inputs": {
245 "title": {
246 "description": "A title or an author, as in: The Left Hand of Darkness",
247 "required": true,
248 "type": "str"
249 }
250 },
251 "language": {},
252 "license": "MIT",
253 "name": "books",
254 "namespace": "pyrtan",
255 "sources": [
256 "http"
257 ],
258 "tags": [
259 "reference",
260 "books",
261 "read-only"
262 ],
263 "triggers": [
264 "who wrote {title}",
265 "find the book {title}",
266 "books by {title}"
267 ],
268 "version": 1
269 },
270 {
271 "description": "A random fact about cats, from catfact.ninja.",
272 "hosts": [
273 "catfact.ninja"
274 ],
275 "inputs": {},
276 "language": {},
277 "license": "MIT",
278 "name": "cat-fact",
279 "namespace": "pyrtan",
280 "sources": [
281 "http"
282 ],
283 "tags": [
284 "fun",
285 "animals",
286 "read-only"
287 ],
288 "triggers": [
289 "[tell me a] cat fact",
290 "tell me something about cats"
291 ],
292 "version": 1
293 },
294 {
295 "description": "echo for count",
296 "hosts": [],
297 "inputs": {
298 "n": {
299 "type": "float"
300 },
301 "x": {
302 "type": "float"
303 },
304 "y": {
305 "type": "int"
306 }
307 },
308 "language": {},
309 "license": "",
310 "name": "count",
311 "sources": [],
312 "tags": [],
313 "triggers": [
314 "count to {n:number}",
315 "add {x:float} and {y:int}"
316 ],
317 "version": 1
318 },
319 {
320 "description": "A random dad joke, from icanhazdadjoke.com.",
321 "hosts": [
322 "icanhazdadjoke.com"
323 ],
324 "inputs": {},
325 "language": {},
326 "license": "MIT",
327 "name": "dad-joke",
328 "namespace": "pyrtan",
329 "sources": [
330 "http"
331 ],
332 "tags": [
333 "fun",
334 "read-only"
335 ],
336 "triggers": [
337 "tell me a [dad] joke",
338 "[a] dad joke",
339 "make me laugh"
340 ],
341 "version": 1
342 },
343 {
344 "description": "What an English word means: its first definition, part of speech and pronunciation.",
345 "hosts": [
346 "api.dictionaryapi.dev"
347 ],
348 "inputs": {
349 "word": {
350 "description": "One English word",
351 "required": true,
352 "type": "str"
353 }
354 },
355 "language": {},
356 "license": "MIT",
357 "name": "define",
358 "namespace": "pyrtan",
359 "sources": [
360 "http"
361 ],
362 "tags": [
363 "reference",
364 "language",
365 "read-only"
366 ],
367 "triggers": [
368 "define {word:word}",
369 "what does {word:word} mean",
370 "[the] (meaning|definition) of {word:word}"
371 ],
372 "version": 1
373 },
374 {
375 "commands": [
376 "df"
377 ],
378 "description": "How full a filesystem is, from df on the box this tree runs on.",
379 "hosts": [],
380 "inputs": {
381 "mount": {
382 "default": "/",
383 "description": "A mount point, or any path on the filesystem to measure",
384 "type": "str"
385 }
386 },
387 "language": {},
388 "license": "MIT",
389 "name": "disk-space",
390 "namespace": "pyrtan",
391 "sources": [
392 "shell"
393 ],
394 "tags": [
395 "ops",
396 "box",
397 "read-only"
398 ],
399 "triggers": [
400 "[how] full is the disk",
401 "disk space",
402 "disk space on {mount:word}"
403 ],
404 "version": 2
405 },
406 {
407 "description": "Magnitude 4.5+ earthquakes worldwide in the past day, from the USGS: how many, and the latest.",
408 "hosts": [
409 "earthquake.usgs.gov"
410 ],
411 "inputs": {},
412 "language": {},
413 "license": "MIT",
414 "name": "earthquakes",
415 "namespace": "pyrtan",
416 "sources": [
417 "http"
418 ],
419 "tags": [
420 "science",
421 "read-only"
422 ],
423 "triggers": [
424 "[any] [recent] earthquakes [today]",
425 "[was there an] earthquake today",
426 "latest earthquake"
427 ],
428 "version": 1
429 },
430 {
431 "description": "How high a place is above sea level, from Open-Meteo's geocoder.",
432 "hosts": [
433 "geocoding-api.open-meteo.com"
434 ],
435 "inputs": {
436 "place": {
437 "description": "A town or city name",
438 "required": true,
439 "type": "str"
440 }
441 },
442 "language": {},
443 "license": "MIT",
444 "name": "elevation",
445 "namespace": "pyrtan",
446 "sources": [
447 "http"
448 ],
449 "tags": [
450 "geography",
451 "read-only"
452 ],
453 "triggers": [
454 "how high (is|above sea level is) {place}",
455 "[the] (elevation|altitude) of {place}"
456 ],
457 "version": 1
458 },
459 {
460 "description": "An amount in another currency at today's European Central Bank reference rate, from Frankfurter.",
461 "hosts": [
462 "api.frankfurter.dev"
463 ],
464 "inputs": {
465 "amount": {
466 "default": "1",
467 "description": "How much of the first currency, as in: 100 or 2.5",
468 "type": "str"
469 },
470 "from": {
471 "description": "A three-letter currency code, as in: USD",
472 "required": true,
473 "type": "str"
474 },
475 "to": {
476 "description": "A three-letter currency code, as in: EUR",
477 "required": true,
478 "type": "str"
479 }
480 },
481 "language": {},
482 "license": "MIT",
483 "name": "exchange-rate",
484 "namespace": "pyrtan",
485 "sources": [
486 "http"
487 ],
488 "tags": [
489 "money",
490 "read-only"
491 ],
492 "triggers": [
493 "convert {amount:number} {from:word} (to|into|in) {to:word}",
494 "how much is {amount:number} {from:word} in {to:word}",
495 "[the] exchange rate (from|for) {from:word} to {to:word}",
496 "{from:word} to {to:word} (rate|exchange rate)"
497 ],
498 "version": 1
499 },
500 {
501 "description": "echo for fixed",
502 "hosts": [],
503 "inputs": {
504 "what": {
505 "default": "tea",
506 "type": "str"
507 }
508 },
509 "language": {},
510 "license": "",
511 "name": "fixed",
512 "sources": [],
513 "tags": [],
514 "trigger_inputs": {
515 "the usual": {
516 "what": "coffee"
517 }
518 },
519 "triggers": [
520 "the usual"
521 ],
522 "version": 1
523 },
524 {
525 "description": "Tomorrow's weather for a place: sky, high and low, and chance of rain, from Open-Meteo.",
526 "hosts": [
527 "geocoding-api.open-meteo.com",
528 "api.open-meteo.com"
529 ],
530 "inputs": {
531 "place": {
532 "description": "A town or city name",
533 "required": true,
534 "type": "str"
535 }
536 },
537 "language": {},
538 "license": "MIT",
539 "name": "forecast",
540 "namespace": "pyrtan",
541 "sources": [
542 "http"
543 ],
544 "tags": [
545 "weather",
546 "read-only"
547 ],
548 "triggers": [
549 "[what's the] weather (in|for) {place} tomorrow",
550 "[what's the] weather tomorrow (in|for) {place}",
551 "will it rain tomorrow in {place}"
552 ],
553 "version": 1
554 },
555 {
556 "description": "The latest release of a GitHub repository.",
557 "hosts": [
558 "api.github.com"
559 ],
560 "inputs": {
561 "repo": {
562 "description": "owner/name",
563 "required": true,
564 "type": "str"
565 }
566 },
567 "language": {},
568 "license": "MIT",
569 "name": "github-release",
570 "namespace": "pyrtan",
571 "sources": [
572 "http"
573 ],
574 "tags": [
575 "github",
576 "read-only"
577 ],
578 "triggers": [
579 "[what's the] latest release of {repo:word}",
580 "latest {repo:word} release"
581 ],
582 "version": 2
583 },
584 {
585 "description": "A GitHub repository at a glance: description, language, stars, forks, open issues.",
586 "hosts": [
587 "api.github.com"
588 ],
589 "inputs": {
590 "repo": {
591 "description": "owner/name, as in: astral-sh/uv",
592 "required": true,
593 "type": "str"
594 }
595 },
596 "language": {},
597 "license": "MIT",
598 "name": "github-repo",
599 "namespace": "pyrtan",
600 "sources": [
601 "http"
602 ],
603 "tags": [
604 "github",
605 "software",
606 "read-only"
607 ],
608 "triggers": [
609 "how many stars does {repo:word} have",
610 "github (repo|repository) {repo:word}",
611 "tell me about the {repo:word} repo"
612 ],
613 "version": 1
614 },
615 {
616 "description": "The latest version of a Go module, and when it was published, from proxy.golang.org.",
617 "hosts": [
618 "proxy.golang.org"
619 ],
620 "inputs": {
621 "module": {
622 "description": "A module path, as in: github.com/gorilla/mux",
623 "required": true,
624 "type": "str"
625 }
626 },
627 "language": {},
628 "license": "MIT",
629 "name": "go-module",
630 "namespace": "pyrtan",
631 "sources": [
632 "http"
633 ],
634 "tags": [
635 "software",
636 "go",
637 "read-only"
638 ],
639 "triggers": [
640 "[what's the] latest version of {module:word} on go",
641 "go (module|version) {module:word}"
642 ],
643 "version": 1
644 },
645 {
646 "description": "Top stories on Hacker News, from its public API.",
647 "hosts": [
648 "hacker-news.firebaseio.com"
649 ],
650 "inputs": {
651 "count": {
652 "default": 1,
653 "description": "How many stories to return",
654 "type": "int"
655 }
656 },
657 "language": {},
658 "license": "MIT",
659 "name": "hacker-news",
660 "namespace": "pyrtan",
661 "sources": [
662 "http"
663 ],
664 "tags": [
665 "news",
666 "read-only"
667 ],
668 "trigger_inputs": {
669 "[what's the] top story on hacker news": {
670 "count": 1
671 }
672 },
673 "triggers": [
674 "[what's the] top story on hacker news",
675 "top {count:int} stories on hacker news"
676 ],
677 "version": 2
678 },
679 {
680 "description": "One Hacker News item by id: title, url, score, author.",
681 "hosts": [
682 "hacker-news.firebaseio.com"
683 ],
684 "inputs": {
685 "id": {
686 "description": "The item id",
687 "required": true,
688 "type": "int"
689 }
690 },
691 "language": {},
692 "license": "MIT",
693 "name": "hacker-news-item",
694 "namespace": "pyrtan",
695 "sources": [
696 "http"
697 ],
698 "tags": [
699 "news",
700 "read-only"
701 ],
702 "triggers": [
703 "hacker news item {id:int}"
704 ],
705 "version": 2
706 },
707 {
708 "description": "Hacker News stories about something, through HN Search: the top three matches.",
709 "hosts": [
710 "hn.algolia.com"
711 ],
712 "inputs": {
713 "query": {
714 "description": "Words to search story titles and text for",
715 "required": true,
716 "type": "str"
717 }
718 },
719 "language": {},
720 "license": "MIT",
721 "name": "hn-search",
722 "namespace": "pyrtan",
723 "sources": [
724 "http"
725 ],
726 "tags": [
727 "news",
728 "tech",
729 "read-only"
730 ],
731 "triggers": [
732 "search hacker news for {query}",
733 "hacker news stories about {query}",
734 "what does hacker news say about {query}"
735 ],
736 "version": 1
737 },
738 {
739 "description": "The next public holiday in a country (by its two-letter code), from Nager.Date.",
740 "hosts": [
741 "date.nager.at"
742 ],
743 "inputs": {
744 "country": {
745 "description": "A two-letter country code, as in: NO, US, DE",
746 "required": true,
747 "type": "str"
748 }
749 },
750 "language": {},
751 "license": "MIT",
752 "name": "holidays",
753 "namespace": "pyrtan",
754 "sources": [
755 "http"
756 ],
757 "tags": [
758 "calendar",
759 "read-only"
760 ],
761 "triggers": [
762 "[when is the] next [public|bank] holiday in {country:word}",
763 "[public|bank] holidays in {country:word}"
764 ],
765 "version": 1
766 },
767 {
768 "description": "A Homebrew formula's current stable version and what it is, from formulae.brew.sh.",
769 "hosts": [
770 "formulae.brew.sh"
771 ],
772 "inputs": {
773 "formula": {
774 "description": "A formula name, as in: wget",
775 "required": true,
776 "type": "str"
777 }
778 },
779 "language": {},
780 "license": "MIT",
781 "name": "homebrew",
782 "namespace": "pyrtan",
783 "sources": [
784 "http"
785 ],
786 "tags": [
787 "software",
788 "macos",
789 "read-only"
790 ],
791 "triggers": [
792 "[what's the] latest version of {formula:word} on homebrew",
793 "(brew|homebrew) (formula|version) {formula:word}"
794 ],
795 "version": 1
796 },
797 {
798 "description": "Where the International Space Station is right now: latitude, longitude, altitude and speed.",
799 "hosts": [
800 "api.wheretheiss.at"
801 ],
802 "inputs": {},
803 "language": {},
804 "license": "MIT",
805 "name": "iss",
806 "namespace": "pyrtan",
807 "sources": [
808 "http"
809 ],
810 "tags": [
811 "science",
812 "space",
813 "read-only"
814 ],
815 "triggers": [
816 "where is the (iss|space station) [now]",
817 "(iss|space station) (position|location)"
818 ],
819 "version": 1
820 },
821 {
822 "description": "echo for library",
823 "hosts": [],
824 "inputs": {
825 "author": {
826 "type": "str"
827 },
828 "count": {
829 "default": 3,
830 "type": "int"
831 }
832 },
833 "language": {
834 "synonyms": {
835 "books": [
836 "novels",
837 "paperback books"
838 ],
839 "find": [
840 "look up"
841 ]
842 },
843 "typos": true
844 },
845 "license": "",
846 "name": "library",
847 "sources": [],
848 "tags": [],
849 "triggers": [
850 "(find|search) books by {author}",
851 "top story on book club"
852 ],
853 "version": 1
854 },
855 {
856 "description": "What time it is now in a place, and its time zone.",
857 "hosts": [
858 "geocoding-api.open-meteo.com",
859 "timeapi.io"
860 ],
861 "inputs": {
862 "place": {
863 "description": "A town or city name",
864 "required": true,
865 "type": "str"
866 }
867 },
868 "language": {},
869 "license": "MIT",
870 "name": "local-time",
871 "namespace": "pyrtan",
872 "sources": [
873 "http"
874 ],
875 "tags": [
876 "time",
877 "read-only"
878 ],
879 "triggers": [
880 "what time is it in {place}",
881 "[the] (time|local time) in {place}"
882 ],
883 "version": 1
884 },
885 {
886 "description": "Error lines in the tail of a log file on this box.",
887 "hosts": [],
888 "inputs": {
889 "lines": {
890 "default": 200,
891 "description": "How many lines from the end to look at",
892 "type": "int"
893 },
894 "path": {
895 "default": "/var/log/syslog",
896 "type": "str"
897 }
898 },
899 "language": {},
900 "license": "MIT",
901 "name": "log-errors",
902 "namespace": "pyrtan",
903 "paths": [
904 "/var/log"
905 ],
906 "sources": [
907 "file"
908 ],
909 "tags": [
910 "ops",
911 "box",
912 "read-only"
913 ],
914 "triggers": [
915 "errors in the log",
916 "errors in {path:word}"
917 ],
918 "version": 2
919 },
920 {
921 "description": "Whether a modem on a serial line answers AT.",
922 "devices": [
923 "/dev/ttyUSB*",
924 "/dev/ttyACM*"
925 ],
926 "hosts": [],
927 "inputs": {
928 "device": {
929 "default": "/dev/ttyUSB0",
930 "type": "str"
931 }
932 },
933 "language": {},
934 "license": "MIT",
935 "name": "modem-check",
936 "namespace": "pyrtan",
937 "sources": [
938 "serial"
939 ],
940 "tags": [
941 "hardware",
942 "serial"
943 ],
944 "triggers": [
945 "is the modem up",
946 "is the modem on {device:word} up"
947 ],
948 "version": 2
949 },
950 {
951 "description": "The public IP address requests from here come from, from ipify.",
952 "hosts": [
953 "api.ipify.org"
954 ],
955 "inputs": {},
956 "language": {},
957 "license": "MIT",
958 "name": "my-ip",
959 "namespace": "pyrtan",
960 "sources": [
961 "http"
962 ],
963 "tags": [
964 "network",
965 "read-only"
966 ],
967 "triggers": [
968 "what is my [public] ip [address]",
969 "my [public] ip [address]"
970 ],
971 "version": 1
972 },
973 {
974 "description": "The next rocket launch anywhere, when, and its status, from the Launch Library.",
975 "hosts": [
976 "ll.thespacedevs.com"
977 ],
978 "inputs": {},
979 "language": {},
980 "license": "MIT",
981 "name": "next-launch",
982 "namespace": "pyrtan",
983 "sources": [
984 "http"
985 ],
986 "tags": [
987 "space",
988 "read-only"
989 ],
990 "triggers": [
991 "[when is the] next (launch|rocket launch)",
992 "upcoming (launches|rocket launches)"
993 ],
994 "version": 1
995 },
996 {
997 "description": "The latest version of an npm package, and what it says it is.",
998 "hosts": [
999 "registry.npmjs.org"
1000 ],
1001 "inputs": {
1002 "package": {
1003 "description": "A package name, as in: react or @types/node",
1004 "required": true,
1005 "type": "str"
1006 }
1007 },
1008 "language": {},
1009 "license": "MIT",
1010 "name": "npm-package",
1011 "namespace": "pyrtan",
1012 "sources": [
1013 "http"
1014 ],
1015 "tags": [
1016 "software",
1017 "javascript",
1018 "read-only"
1019 ],
1020 "triggers": [
1021 "[what's the] latest version of {package:word} on npm",
1022 "latest {package:word} on npm",
1023 "npm (package|version) {package:word}"
1024 ],
1025 "version": 1
1026 },
1027 {
1028 "description": "asks, then answers",
1029 "hosts": [],
1030 "inputs": {
1031 "drink": {
1032 "required": true,
1033 "type": "str"
1034 },
1035 "shots": {
1036 "default": 1,
1037 "type": "int"
1038 }
1039 },
1040 "language": {},
1041 "license": "",
1042 "name": "order",
1043 "sources": [],
1044 "tags": [],
1045 "triggers": [
1046 "order a {drink:word}"
1047 ],
1048 "version": 1
1049 },
1050 {
1051 "description": "echo for ping",
1052 "hosts": [],
1053 "inputs": {
1054 "host": {
1055 "type": "str"
1056 },
1057 "to": {
1058 "type": "str"
1059 },
1060 "url": {
1061 "type": "str"
1062 }
1063 },
1064 "language": {},
1065 "license": "",
1066 "name": "ping",
1067 "sources": [],
1068 "tags": [],
1069 "triggers": [
1070 "ping {host:host}",
1071 "fetch {url:url}",
1072 "mail {to:email}"
1073 ],
1074 "version": 1
1075 },
1076 {
1077 "description": "A Pokémon's number, types, height and weight, from PokéAPI.",
1078 "hosts": [
1079 "pokeapi.co"
1080 ],
1081 "inputs": {
1082 "name": {
1083 "description": "A Pokémon's name or number, as in: pikachu",
1084 "required": true,
1085 "type": "str"
1086 }
1087 },
1088 "language": {},
1089 "license": "MIT",
1090 "name": "pokemon",
1091 "namespace": "pyrtan",
1092 "sources": [
1093 "http"
1094 ],
1095 "tags": [
1096 "fun",
1097 "games",
1098 "read-only"
1099 ],
1100 "triggers": [
1101 "pokemon {name:word}",
1102 "what type is {name:word}",
1103 "tell me about the pokemon {name:word}"
1104 ],
1105 "version": 1
1106 },
1107 {
1108 "description": "The place a postal code belongs to, from Zippopotam.us (US ZIP codes, or a code in another country).",
1109 "hosts": [
1110 "api.zippopotam.us"
1111 ],
1112 "inputs": {
1113 "code": {
1114 "description": "A postal code, as in: 90210",
1115 "required": true,
1116 "type": "str"
1117 },
1118 "country": {
1119 "default": "us",
1120 "description": "A two-letter country code, as in: US, DE, GB",
1121 "type": "str"
1122 }
1123 },
1124 "language": {},
1125 "license": "MIT",
1126 "name": "postal-code",
1127 "namespace": "pyrtan",
1128 "sources": [
1129 "http"
1130 ],
1131 "tags": [
1132 "geography",
1133 "read-only"
1134 ],
1135 "trigger_inputs": {
1136 "where is zip [code] {code:word}": {
1137 "country": "us"
1138 }
1139 },
1140 "triggers": [
1141 "where is zip [code] {code:word}",
1142 "[where is] (postcode|postal code) {code:word} in {country:word}"
1143 ],
1144 "version": 1
1145 },
1146 {
1147 "description": "The latest version of a Python package on PyPI, and what it says it is.",
1148 "hosts": [
1149 "pypi.org"
1150 ],
1151 "inputs": {
1152 "package": {
1153 "description": "A project name, as in: requests",
1154 "required": true,
1155 "type": "str"
1156 }
1157 },
1158 "language": {},
1159 "license": "MIT",
1160 "name": "pypi-package",
1161 "namespace": "pyrtan",
1162 "sources": [
1163 "http"
1164 ],
1165 "tags": [
1166 "software",
1167 "python",
1168 "read-only"
1169 ],
1170 "triggers": [
1171 "[what's the] latest version of {package:word} on pypi",
1172 "latest {package:word} on pypi",
1173 "(pypi|pip) (package|version) {package:word}"
1174 ],
1175 "version": 1
1176 },
1177 {
1178 "description": "echo for remind",
1179 "hosts": [],
1180 "inputs": {
1181 "at": {
1182 "type": "str"
1183 },
1184 "wait": {
1185 "type": "int"
1186 },
1187 "when": {
1188 "type": "str"
1189 }
1190 },
1191 "language": {},
1192 "license": "",
1193 "name": "remind",
1194 "sources": [],
1195 "tags": [],
1196 "triggers": [
1197 "remind me {when:date} at {at:time}",
1198 "remind me in {wait:duration}"
1199 ],
1200 "version": 1
1201 },
1202 {
1203 "description": "The main heading of a page that needs a script to render, through a real browser.",
1204 "hosts": [
1205 "*"
1206 ],
1207 "inputs": {
1208 "url": {
1209 "required": true,
1210 "type": "str"
1211 }
1212 },
1213 "language": {},
1214 "license": "MIT",
1215 "name": "rendered-headline",
1216 "namespace": "pyrtan",
1217 "sources": [
1218 "browser"
1219 ],
1220 "tags": [
1221 "web",
1222 "browser",
1223 "read-only"
1224 ],
1225 "triggers": [
1226 "headline of {url:url}",
1227 "what does {url:url} say"
1228 ],
1229 "version": 2
1230 },
1231 {
1232 "description": "Words that rhyme with a word, from Datamuse.",
1233 "hosts": [
1234 "api.datamuse.com"
1235 ],
1236 "inputs": {
1237 "word": {
1238 "description": "One English word",
1239 "required": true,
1240 "type": "str"
1241 }
1242 },
1243 "language": {},
1244 "license": "MIT",
1245 "name": "rhymes",
1246 "namespace": "pyrtan",
1247 "sources": [
1248 "http"
1249 ],
1250 "tags": [
1251 "language",
1252 "read-only"
1253 ],
1254 "triggers": [
1255 "what rhymes with {word:word}",
1256 "(rhymes|words that rhyme) (for|with) {word:word}"
1257 ],
1258 "version": 1
1259 },
1260 {
1261 "description": "The temperature a room's sensor last published, over MQTT.",
1262 "hosts": [
1263 "broker.local"
1264 ],
1265 "inputs": {
1266 "room": {
1267 "default": "kitchen",
1268 "type": "str"
1269 }
1270 },
1271 "language": {},
1272 "license": "MIT",
1273 "name": "room-temperature",
1274 "namespace": "pyrtan",
1275 "sources": [
1276 "queue"
1277 ],
1278 "tags": [
1279 "home",
1280 "mqtt"
1281 ],
1282 "topics": [
1283 "home/+/temperature"
1284 ],
1285 "triggers": [
1286 "[what's the] temperature in the {room:word}",
1287 "how warm is the {room:word}"
1288 ],
1289 "version": 2,
1290 "watches": [
1291 {
1292 "kind": "queue",
1293 "what": "home/kitchen/temperature at mqtt://broker.local"
1294 }
1295 ]
1296 },
1297 {
1298 "description": "Whether a web site answers, and how quickly.",
1299 "hosts": [
1300 "*"
1301 ],
1302 "inputs": {
1303 "host": {
1304 "description": "A host name, with no scheme",
1305 "required": true,
1306 "type": "str"
1307 }
1308 },
1309 "language": {},
1310 "license": "MIT",
1311 "name": "site-status",
1312 "namespace": "pyrtan",
1313 "sources": [
1314 "http"
1315 ],
1316 "tags": [
1317 "ops",
1318 "read-only"
1319 ],
1320 "triggers": [
1321 "is {host:host} up",
1322 "is {host:host} down",
1323 "[check] status of {host:host}"
1324 ],
1325 "version": 2,
1326 "watches": [
1327 {
1328 "kind": "every",
1329 "what": "15m"
1330 }
1331 ]
1332 },
1333 {
1334 "description": "How are you, who are you, what can you do, goodbye: what a stranger asks a bot in the chat before asking anything.",
1335 "hosts": [],
1336 "inputs": {
1337 "topic": {
1338 "required": true,
1339 "type": "str"
1340 }
1341 },
1342 "language": {},
1343 "license": "MIT",
1344 "name": "small-talk",
1345 "namespace": "pyrtan",
1346 "sources": [],
1347 "tags": [
1348 "chat",
1349 "read-only"
1350 ],
1351 "trigger_inputs": {
1352 "(bye|goodbye|see you|see ya)": {
1353 "topic": "bye"
1354 },
1355 "(who|what) are you": {
1356 "topic": "who"
1357 },
1358 "are you (a bot|human|real|a person)": {
1359 "topic": "who"
1360 },
1361 "how are you [doing|today]": {
1362 "topic": "how"
1363 },
1364 "what (can|do) you do": {
1365 "topic": "what"
1366 }
1367 },
1368 "triggers": [
1369 "how are you [doing|today]",
1370 "(who|what) are you",
1371 "what (can|do) you do",
1372 "are you (a bot|human|real|a person)",
1373 "(bye|goodbye|see you|see ya)"
1374 ],
1375 "version": 1
1376 },
1377 {
1378 "description": "The three latest spaceflight news articles, from the Spaceflight News API.",
1379 "hosts": [
1380 "api.spaceflightnewsapi.net"
1381 ],
1382 "inputs": {},
1383 "language": {},
1384 "license": "MIT",
1385 "name": "space-news",
1386 "namespace": "pyrtan",
1387 "sources": [
1388 "http"
1389 ],
1390 "tags": [
1391 "space",
1392 "news",
1393 "read-only"
1394 ],
1395 "triggers": [
1396 "[latest] (space|spaceflight) news",
1397 "what's new in space"
1398 ],
1399 "version": 1
1400 },
1401 {
1402 "description": "Today's sunrise and sunset for a place, in its local time, from Open-Meteo.",
1403 "hosts": [
1404 "geocoding-api.open-meteo.com",
1405 "api.open-meteo.com"
1406 ],
1407 "inputs": {
1408 "place": {
1409 "description": "A town or city name",
1410 "required": true,
1411 "type": "str"
1412 }
1413 },
1414 "language": {},
1415 "license": "MIT",
1416 "name": "sun",
1417 "namespace": "pyrtan",
1418 "sources": [
1419 "http"
1420 ],
1421 "tags": [
1422 "weather",
1423 "read-only"
1424 ],
1425 "triggers": [
1426 "[when is] (sunrise|sunset) in {place}",
1427 "when does the sun (rise|set) in {place}",
1428 "(sunrise|sun) times (in|for) {place}"
1429 ],
1430 "version": 1
1431 },
1432 {
1433 "description": "Synonyms for an English word, best first, from Datamuse.",
1434 "hosts": [
1435 "api.datamuse.com"
1436 ],
1437 "inputs": {
1438 "word": {
1439 "description": "One English word",
1440 "required": true,
1441 "type": "str"
1442 }
1443 },
1444 "language": {},
1445 "license": "MIT",
1446 "name": "synonyms",
1447 "namespace": "pyrtan",
1448 "sources": [
1449 "http"
1450 ],
1451 "tags": [
1452 "reference",
1453 "language",
1454 "read-only"
1455 ],
1456 "triggers": [
1457 "synonyms (for|of) {word:word}",
1458 "(another|other) word for {word:word}",
1459 "what is another word for {word:word}"
1460 ],
1461 "version": 1
1462 },
1463 {
1464 "description": "echo for tie-a",
1465 "hosts": [],
1466 "inputs": {
1467 "x": {
1468 "type": "str"
1469 }
1470 },
1471 "language": {},
1472 "license": "",
1473 "name": "tie-a",
1474 "sources": [],
1475 "tags": [],
1476 "triggers": [
1477 "shout {x:word}"
1478 ],
1479 "version": 1
1480 },
1481 {
1482 "description": "echo for tie-b",
1483 "hosts": [],
1484 "inputs": {
1485 "y": {
1486 "type": "str"
1487 }
1488 },
1489 "language": {},
1490 "license": "",
1491 "name": "tie-b",
1492 "sources": [],
1493 "tags": [],
1494 "triggers": [
1495 "shout {y:word}"
1496 ],
1497 "version": 1
1498 },
1499 {
1500 "description": "The UV index for a place, now and today's highest, with its WHO band, from Open-Meteo.",
1501 "hosts": [
1502 "geocoding-api.open-meteo.com",
1503 "api.open-meteo.com"
1504 ],
1505 "inputs": {
1506 "place": {
1507 "description": "A town or city name",
1508 "required": true,
1509 "type": "str"
1510 }
1511 },
1512 "language": {},
1513 "license": "MIT",
1514 "name": "uv-index",
1515 "namespace": "pyrtan",
1516 "sources": [
1517 "http"
1518 ],
1519 "tags": [
1520 "weather",
1521 "health",
1522 "read-only"
1523 ],
1524 "triggers": [
1525 "[what's the] (uv|uv index) in {place}",
1526 "do i need sunscreen in {place}"
1527 ],
1528 "version": 1
1529 },
1530 {
1531 "description": "The Wayback Machine's latest copy of a site, if it has one.",
1532 "hosts": [
1533 "archive.org"
1534 ],
1535 "inputs": {
1536 "site": {
1537 "description": "A host name, as in: example.com",
1538 "required": true,
1539 "type": "str"
1540 }
1541 },
1542 "language": {},
1543 "license": "MIT",
1544 "name": "wayback",
1545 "namespace": "pyrtan",
1546 "sources": [
1547 "http"
1548 ],
1549 "tags": [
1550 "web",
1551 "read-only"
1552 ],
1553 "triggers": [
1554 "is {site:host} archived",
1555 "wayback [machine] {site:host}",
1556 "[the] archived copy of {site:host}"
1557 ],
1558 "version": 1
1559 },
1560 {
1561 "description": "Current weather for a place, from Open-Meteo.",
1562 "hosts": [
1563 "geocoding-api.open-meteo.com",
1564 "api.open-meteo.com"
1565 ],
1566 "inputs": {
1567 "place": {
1568 "description": "A town or city name",
1569 "required": true,
1570 "type": "str"
1571 },
1572 "units": {
1573 "default": "celsius",
1574 "type": "str"
1575 }
1576 },
1577 "language": {
1578 "synonyms": {
1579 "weather": [
1580 "forecast",
1581 "temperature"
1582 ]
1583 }
1584 },
1585 "license": "MIT",
1586 "name": "weather",
1587 "namespace": "pyrtan",
1588 "sources": [
1589 "http"
1590 ],
1591 "tags": [
1592 "weather",
1593 "read-only"
1594 ],
1595 "triggers": [
1596 "[what's the] weather in {place}",
1597 "[what's the] weather for {place}",
1598 "how [hot|cold|warm] is it in {place}"
1599 ],
1600 "version": 2
1601 },
1602 {
1603 "description": "The summary of a Wikipedia article.",
1604 "hosts": [
1605 "en.wikipedia.org"
1606 ],
1607 "inputs": {
1608 "topic": {
1609 "description": "An article title, as in: Ursula K. Le Guin",
1610 "required": true,
1611 "type": "str"
1612 }
1613 },
1614 "language": {},
1615 "license": "MIT",
1616 "name": "wikipedia",
1617 "namespace": "pyrtan",
1618 "sources": [
1619 "http"
1620 ],
1621 "tags": [
1622 "reference",
1623 "read-only"
1624 ],
1625 "triggers": [
1626 "tell me about {topic}",
1627 "who (is|was) {topic}",
1628 "(wikipedia|wiki) {topic}"
1629 ],
1630 "version": 1
1631 }
1632 ]
1633 },
1634 "method": "GET",
1635 "path": "/capabilities",
1636 "status": 200
1637 },
1638 {
1639 "body": {
1640 "text": "top 3 stories on hacker news"
1641 },
1642 "expect": {
1643 "inputs": {
1644 "count": 3
1645 },
1646 "output": {
1647 "stories": [
1648 {
1649 "by": "alice",
1650 "id": 101,
1651 "score": 42,
1652 "title": "Pyrtan 0.1",
1653 "url": "https://example.org/p"
1654 },
1655 {
1656 "by": "x",
1657 "id": 102,
1658 "score": 7,
1659 "title": "Ask HN: self post",
1660 "url": "https://news.ycombinator.com/item?id=102"
1661 },
1662 null
1663 ]
1664 },
1665 "say": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p",
1666 "status": "done",
1667 "trace": {
1668 "gaps": [],
1669 "steps": [
1670 {
1671 "depth": 0,
1672 "kind": "intent",
1673 "next": "fetch-ids",
1674 "node": "intent",
1675 "observed": {
1676 "cleaned": "top 3 stories on hacker news",
1677 "inputs": {
1678 "count": 3
1679 },
1680 "steps": [],
1681 "trigger": "top {count:int} stories on hacker news"
1682 },
1683 "summary": "'top 3 stories on hacker news'; hacker-news via 'top {count:int} stories on hacker news' (22 literal chars)"
1684 },
1685 {
1686 "depth": 0,
1687 "kind": "observe",
1688 "next": "check",
1689 "node": "fetch-ids",
1690 "observed": {
1691 "ids": [
1692 101,
1693 102,
1694 103
1695 ]
1696 },
1697 "summary": "GET https://hacker-news.firebaseio.com/v0/topstories.json -> 200; ids=[101, 102, 103]"
1698 },
1699 {
1700 "chosen": "eq 200",
1701 "depth": 0,
1702 "kind": "decide",
1703 "next": "any",
1704 "node": "check",
1705 "observed": 200,
1706 "summary": "state.ids_resp.status = 200"
1707 },
1708 {
1709 "chosen": "type 'list'",
1710 "depth": 0,
1711 "kind": "decide",
1712 "next": "stories",
1713 "node": "any",
1714 "observed": [
1715 101,
1716 102,
1717 103
1718 ],
1719 "summary": "state.ids = [101, 102, 103]"
1720 },
1721 {
1722 "depth": 1,
1723 "kind": "observe",
1724 "next": "kind",
1725 "node": "fetch",
1726 "observed": {
1727 "by": "alice",
1728 "score": 42,
1729 "title": "Pyrtan 0.1",
1730 "type": "story",
1731 "url": "https://example.org/p"
1732 },
1733 "summary": "GET https://hacker-news.firebaseio.com/v0/item/101.json -> 200; title=Pyrtan 0.1, url=https://example.org/p, score=42, by=alice, type=story"
1734 },
1735 {
1736 "chosen": "eq 'story'",
1737 "depth": 1,
1738 "kind": "decide",
1739 "next": "has-url",
1740 "node": "kind",
1741 "observed": "story",
1742 "summary": "state.type = story"
1743 },
1744 {
1745 "chosen": "exists True",
1746 "depth": 1,
1747 "kind": "decide",
1748 "next": "reply",
1749 "node": "has-url",
1750 "observed": "https://example.org/p",
1751 "summary": "state.url = https://example.org/p"
1752 },
1753 {
1754 "depth": 1,
1755 "kind": "done",
1756 "next": null,
1757 "node": "reply",
1758 "summary": "Pyrtan 0.1 (42 points, by alice) https://example.org/p"
1759 },
1760 {
1761 "depth": 1,
1762 "kind": "observe",
1763 "next": "kind",
1764 "node": "fetch",
1765 "observed": {
1766 "by": "x",
1767 "score": 7,
1768 "title": "Ask HN: self post",
1769 "type": "story",
1770 "url": null
1771 },
1772 "summary": "GET https://hacker-news.firebaseio.com/v0/item/102.json -> 200; title=Ask HN: self post, url=None, score=7, by=x, type=story"
1773 },
1774 {
1775 "chosen": "eq 'story'",
1776 "depth": 1,
1777 "kind": "decide",
1778 "next": "has-url",
1779 "node": "kind",
1780 "observed": "story",
1781 "summary": "state.type = story"
1782 },
1783 {
1784 "chosen": "else",
1785 "depth": 1,
1786 "kind": "decide",
1787 "next": "self-post",
1788 "node": "has-url",
1789 "summary": "state.url = <missing>"
1790 },
1791 {
1792 "depth": 1,
1793 "kind": "set",
1794 "next": "reply",
1795 "node": "self-post",
1796 "observed": {
1797 "url": "https://news.ycombinator.com/item?id=102"
1798 },
1799 "summary": "url=https://news.ycombinator.com/item?id=102"
1800 },
1801 {
1802 "depth": 1,
1803 "kind": "done",
1804 "next": null,
1805 "node": "reply",
1806 "summary": "Ask HN: self post (7 points, by x) https://news.ycombinator.com/item?id=102"
1807 },
1808 {
1809 "depth": 1,
1810 "kind": "observe",
1811 "next": "kind",
1812 "node": "fetch",
1813 "observed": {
1814 "by": "y",
1815 "score": null,
1816 "title": null,
1817 "type": "comment",
1818 "url": null
1819 },
1820 "summary": "GET https://hacker-news.firebaseio.com/v0/item/103.json -> 200; title=None, url=None, score=None, by=y, type=comment"
1821 },
1822 {
1823 "chosen": "else",
1824 "depth": 1,
1825 "kind": "decide",
1826 "next": "not-a-story",
1827 "node": "kind",
1828 "observed": "comment",
1829 "summary": "state.type = comment"
1830 },
1831 {
1832 "depth": 1,
1833 "kind": "fail",
1834 "next": null,
1835 "node": "not-a-story",
1836 "summary": "not-a-story: Item 103 is a comment, not a story."
1837 },
1838 {
1839 "depth": 0,
1840 "kind": "each",
1841 "next": "reply",
1842 "node": "stories",
1843 "summary": "3 item(s), 1 failed"
1844 },
1845 {
1846 "depth": 0,
1847 "kind": "done",
1848 "next": null,
1849 "node": "reply",
1850 "summary": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p"
1851 }
1852 ],
1853 "tree": "hacker-news"
1854 },
1855 "tree": "hacker-news"
1856 },
1857 "method": "POST",
1858 "path": "/intent",
1859 "status": 200
1860 },
1861 {
1862 "body": {
1863 "text": "weather in Oslo",
1864 "trace": false
1865 },
1866 "expect": {
1867 "inputs": {
1868 "place": "Oslo",
1869 "units": "celsius"
1870 },
1871 "output": {
1872 "country": "Norway",
1873 "humidity": 80,
1874 "place": "Oslo",
1875 "sky": "raining",
1876 "temperature": 4.2,
1877 "unit": "°C",
1878 "wind_kmh": 12.3
1879 },
1880 "say": "Oslo, Norway: 4.2°C, raining, humidity 80%, wind 12.3 km/h.",
1881 "status": "done",
1882 "tree": "weather"
1883 },
1884 "method": "POST",
1885 "path": "/intent",
1886 "status": 200
1887 },
1888 {
1889 "body": {
1890 "text": "how cold is it in Nowhereville"
1891 },
1892 "expect": {
1893 "inputs": {
1894 "place": "Nowhereville",
1895 "units": "celsius"
1896 },
1897 "reason": "unknown-place",
1898 "say": "I could not find a place called Nowhereville.",
1899 "status": "failed",
1900 "trace": {
1901 "gaps": [],
1902 "steps": [
1903 {
1904 "depth": 0,
1905 "kind": "intent",
1906 "next": "geocode",
1907 "node": "intent",
1908 "observed": {
1909 "cleaned": "how cold is it in Nowhereville",
1910 "inputs": {
1911 "place": "Nowhereville"
1912 },
1913 "steps": [],
1914 "trigger": "how [hot|cold|warm] is it in {place}"
1915 },
1916 "summary": "'how cold is it in Nowhereville'; weather via 'how [hot|cold|warm] is it in {place}' (20 literal chars)"
1917 },
1918 {
1919 "depth": 0,
1920 "kind": "observe",
1921 "next": "found",
1922 "node": "geocode",
1923 "observed": {
1924 "country": null,
1925 "hit": null,
1926 "lat": null,
1927 "lon": null,
1928 "name": null
1929 },
1930 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Nowhereville&count=1&language=en&format=json -> 200; hit=None, lat=None, lon=None, name=None, country=None"
1931 },
1932 {
1933 "chosen": "else",
1934 "depth": 0,
1935 "kind": "decide",
1936 "next": "unknown-place",
1937 "node": "found",
1938 "summary": "state.hit = <missing>"
1939 },
1940 {
1941 "depth": 0,
1942 "kind": "fail",
1943 "next": null,
1944 "node": "unknown-place",
1945 "summary": "unknown-place: I could not find a place called Nowhereville."
1946 }
1947 ],
1948 "tree": "weather"
1949 },
1950 "tree": "weather"
1951 },
1952 "method": "POST",
1953 "path": "/intent",
1954 "status": 200
1955 },
1956 {
1957 "body": {
1958 "text": "is broken.example up"
1959 },
1960 "expect": {
1961 "inputs": {
1962 "host": "broken.example"
1963 },
1964 "output": {
1965 "host": "broken.example",
1966 "status": 503,
1967 "up": false
1968 },
1969 "reason": "server-error",
1970 "say": "broken.example is answering 503: up, but broken.",
1971 "status": "failed",
1972 "trace": {
1973 "gaps": [],
1974 "steps": [
1975 {
1976 "depth": 0,
1977 "kind": "intent",
1978 "next": "probe",
1979 "node": "intent",
1980 "observed": {
1981 "cleaned": "is broken.example up",
1982 "inputs": {
1983 "host": "broken.example"
1984 },
1985 "steps": [],
1986 "trigger": "is {host:host} up"
1987 },
1988 "summary": "'is broken.example up'; site-status via 'is {host:host} up' (4 literal chars)"
1989 },
1990 {
1991 "depth": 0,
1992 "kind": "observe",
1993 "next": "status",
1994 "node": "probe",
1995 "summary": "GET https://broken.example/ -> 503"
1996 },
1997 {
1998 "chosen": "gte 500",
1999 "depth": 0,
2000 "kind": "decide",
2001 "next": "erroring",
2002 "node": "status",
2003 "observed": 503,
2004 "summary": "state.resp.status = 503"
2005 },
2006 {
2007 "depth": 0,
2008 "kind": "fail",
2009 "next": null,
2010 "node": "erroring",
2011 "summary": "server-error: broken.example is answering 503: up, but broken."
2012 }
2013 ],
2014 "tree": "site-status"
2015 },
2016 "tree": "site-status"
2017 },
2018 "method": "POST",
2019 "path": "/intent",
2020 "status": 200
2021 },
2022 {
2023 "body": {
2024 "text": "is broken.example up"
2025 },
2026 "expect": {
2027 "inputs": {
2028 "host": "broken.example"
2029 },
2030 "output": {
2031 "host": "broken.example",
2032 "status": 503,
2033 "up": false
2034 },
2035 "reason": "server-error",
2036 "say": "broken.example is answering 503: up, but broken.",
2037 "status": "failed",
2038 "trace": {
2039 "gaps": [],
2040 "steps": [
2041 {
2042 "depth": 0,
2043 "kind": "intent",
2044 "next": "probe",
2045 "node": "intent",
2046 "observed": {
2047 "cleaned": "is broken.example up",
2048 "inputs": {
2049 "host": "broken.example"
2050 },
2051 "steps": [],
2052 "trigger": "is {host:host} up"
2053 },
2054 "summary": "'is broken.example up'; site-status via 'is {host:host} up' (4 literal chars)"
2055 },
2056 {
2057 "depth": 0,
2058 "kind": "observe",
2059 "next": "status",
2060 "node": "probe",
2061 "summary": "GET https://broken.example/ -> 503"
2062 },
2063 {
2064 "chosen": "gte 500",
2065 "depth": 0,
2066 "kind": "decide",
2067 "next": "erroring",
2068 "node": "status",
2069 "observed": 503,
2070 "summary": "state.resp.status = 503"
2071 },
2072 {
2073 "depth": 0,
2074 "kind": "fail",
2075 "next": null,
2076 "node": "erroring",
2077 "summary": "server-error: broken.example is answering 503: up, but broken."
2078 }
2079 ],
2080 "tree": "site-status"
2081 },
2082 "tree": "site-status"
2083 },
2084 "method": "POST",
2085 "path": "/intent",
2086 "status": 200
2087 },
2088 {
2089 "body": {
2090 "text": "make me a sandwich"
2091 },
2092 "expect": {
2093 "inputs": {},
2094 "output": {
2095 "proposal": {
2096 "inputs": {},
2097 "name": "make-sandwich",
2098 "scaffold": "pyrtan new make-sandwich --from \"make me a sandwich\"",
2099 "text": "make me a sandwich",
2100 "trigger": "make me a sandwich"
2101 },
2102 "reading": {
2103 "cleaned": "make me a sandwich",
2104 "considered": [],
2105 "steps": []
2106 },
2107 "unanswered": "make me a sandwich"
2108 },
2109 "reason": "no-match",
2110 "say": "Nothing here answers 'make me a sandwich'. To write the tree that would: pyrtan new make-sandwich --from \"make me a sandwich\"",
2111 "status": "error",
2112 "tree": ""
2113 },
2114 "method": "POST",
2115 "path": "/intent",
2116 "status": 200
2117 },
2118 {
2119 "body": {
2120 "text": "Make me a sandwich!"
2121 },
2122 "expect": {
2123 "inputs": {},
2124 "output": {
2125 "proposal": {
2126 "inputs": {},
2127 "name": "make-sandwich",
2128 "scaffold": "pyrtan new make-sandwich --from \"Make me a sandwich\"",
2129 "text": "Make me a sandwich",
2130 "trigger": "Make me a sandwich"
2131 },
2132 "reading": {
2133 "cleaned": "Make me a sandwich",
2134 "considered": [],
2135 "steps": []
2136 },
2137 "unanswered": "Make me a sandwich!"
2138 },
2139 "reason": "no-match",
2140 "say": "Nothing here answers 'Make me a sandwich!'. To write the tree that would: pyrtan new make-sandwich --from \"Make me a sandwich\"",
2141 "status": "error",
2142 "tree": ""
2143 },
2144 "method": "POST",
2145 "path": "/intent",
2146 "status": 200
2147 },
2148 {
2149 "body": {
2150 "text": "shout hello"
2151 },
2152 "expect": {
2153 "inputs": {},
2154 "output": {
2155 "candidates": [
2156 "tie-a",
2157 "tie-b"
2158 ],
2159 "reading": {
2160 "cleaned": "shout hello",
2161 "steps": []
2162 }
2163 },
2164 "reason": "ambiguous",
2165 "say": "'shout hello' matches more than one tree equally well: tie-a, tie-b",
2166 "status": "error",
2167 "tree": ""
2168 },
2169 "method": "POST",
2170 "path": "/intent",
2171 "status": 200
2172 },
2173 {
2174 "body": {
2175 "text": " "
2176 },
2177 "expect": {
2178 "reason": "bad-request",
2179 "say": "Send {\"text\": \"...\"}.",
2180 "status": "error"
2181 },
2182 "method": "POST",
2183 "path": "/intent",
2184 "status": 200
2185 },
2186 {
2187 "body": {},
2188 "expect": {
2189 "reason": "bad-request",
2190 "say": "Send {\"text\": \"...\"}.",
2191 "status": "error"
2192 },
2193 "method": "POST",
2194 "path": "/intent",
2195 "status": 200
2196 },
2197 {
2198 "body": {
2199 "inputs": {
2200 "count": 2
2201 },
2202 "tree": "hacker-news"
2203 },
2204 "expect": {
2205 "inputs": {
2206 "count": 2
2207 },
2208 "output": {
2209 "stories": [
2210 {
2211 "by": "alice",
2212 "id": 101,
2213 "score": 42,
2214 "title": "Pyrtan 0.1",
2215 "url": "https://example.org/p"
2216 },
2217 {
2218 "by": "x",
2219 "id": 102,
2220 "score": 7,
2221 "title": "Ask HN: self post",
2222 "url": "https://news.ycombinator.com/item?id=102"
2223 }
2224 ]
2225 },
2226 "say": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p",
2227 "status": "done",
2228 "trace": {
2229 "gaps": [],
2230 "steps": [
2231 {
2232 "depth": 0,
2233 "kind": "observe",
2234 "next": "check",
2235 "node": "fetch-ids",
2236 "observed": {
2237 "ids": [
2238 101,
2239 102,
2240 103
2241 ]
2242 },
2243 "summary": "GET https://hacker-news.firebaseio.com/v0/topstories.json -> 200; ids=[101, 102, 103]"
2244 },
2245 {
2246 "chosen": "eq 200",
2247 "depth": 0,
2248 "kind": "decide",
2249 "next": "any",
2250 "node": "check",
2251 "observed": 200,
2252 "summary": "state.ids_resp.status = 200"
2253 },
2254 {
2255 "chosen": "type 'list'",
2256 "depth": 0,
2257 "kind": "decide",
2258 "next": "stories",
2259 "node": "any",
2260 "observed": [
2261 101,
2262 102,
2263 103
2264 ],
2265 "summary": "state.ids = [101, 102, 103]"
2266 },
2267 {
2268 "depth": 1,
2269 "kind": "observe",
2270 "next": "kind",
2271 "node": "fetch",
2272 "observed": {
2273 "by": "alice",
2274 "score": 42,
2275 "title": "Pyrtan 0.1",
2276 "type": "story",
2277 "url": "https://example.org/p"
2278 },
2279 "summary": "GET https://hacker-news.firebaseio.com/v0/item/101.json -> 200; title=Pyrtan 0.1, url=https://example.org/p, score=42, by=alice, type=story"
2280 },
2281 {
2282 "chosen": "eq 'story'",
2283 "depth": 1,
2284 "kind": "decide",
2285 "next": "has-url",
2286 "node": "kind",
2287 "observed": "story",
2288 "summary": "state.type = story"
2289 },
2290 {
2291 "chosen": "exists True",
2292 "depth": 1,
2293 "kind": "decide",
2294 "next": "reply",
2295 "node": "has-url",
2296 "observed": "https://example.org/p",
2297 "summary": "state.url = https://example.org/p"
2298 },
2299 {
2300 "depth": 1,
2301 "kind": "done",
2302 "next": null,
2303 "node": "reply",
2304 "summary": "Pyrtan 0.1 (42 points, by alice) https://example.org/p"
2305 },
2306 {
2307 "depth": 1,
2308 "kind": "observe",
2309 "next": "kind",
2310 "node": "fetch",
2311 "observed": {
2312 "by": "x",
2313 "score": 7,
2314 "title": "Ask HN: self post",
2315 "type": "story",
2316 "url": null
2317 },
2318 "summary": "GET https://hacker-news.firebaseio.com/v0/item/102.json -> 200; title=Ask HN: self post, url=None, score=7, by=x, type=story"
2319 },
2320 {
2321 "chosen": "eq 'story'",
2322 "depth": 1,
2323 "kind": "decide",
2324 "next": "has-url",
2325 "node": "kind",
2326 "observed": "story",
2327 "summary": "state.type = story"
2328 },
2329 {
2330 "chosen": "else",
2331 "depth": 1,
2332 "kind": "decide",
2333 "next": "self-post",
2334 "node": "has-url",
2335 "summary": "state.url = <missing>"
2336 },
2337 {
2338 "depth": 1,
2339 "kind": "set",
2340 "next": "reply",
2341 "node": "self-post",
2342 "observed": {
2343 "url": "https://news.ycombinator.com/item?id=102"
2344 },
2345 "summary": "url=https://news.ycombinator.com/item?id=102"
2346 },
2347 {
2348 "depth": 1,
2349 "kind": "done",
2350 "next": null,
2351 "node": "reply",
2352 "summary": "Ask HN: self post (7 points, by x) https://news.ycombinator.com/item?id=102"
2353 },
2354 {
2355 "depth": 0,
2356 "kind": "each",
2357 "next": "reply",
2358 "node": "stories",
2359 "summary": "2 item(s)"
2360 },
2361 {
2362 "depth": 0,
2363 "kind": "done",
2364 "next": null,
2365 "node": "reply",
2366 "summary": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p"
2367 }
2368 ],
2369 "tree": "hacker-news"
2370 },
2371 "tree": "hacker-news"
2372 },
2373 "method": "POST",
2374 "path": "/run",
2375 "status": 200
2376 },
2377 {
2378 "body": {
2379 "tree": "nope"
2380 },
2381 "expect": {
2382 "inputs": {},
2383 "reason": "no-such-tree",
2384 "say": "No tree named 'nope'.",
2385 "status": "error",
2386 "tree": "nope"
2387 },
2388 "method": "POST",
2389 "path": "/run",
2390 "status": 200
2391 },
2392 {
2393 "body": {
2394 "inputs": {
2395 "place": "Oslo",
2396 "units": "kelvin"
2397 },
2398 "tree": "weather"
2399 },
2400 "expect": {
2401 "inputs": {},
2402 "reason": "bad-input",
2403 "say": "input 'units' must be one of ['celsius', 'fahrenheit'], got 'kelvin'",
2404 "status": "error",
2405 "trace": {
2406 "gaps": [],
2407 "steps": [],
2408 "tree": "weather"
2409 },
2410 "tree": "weather"
2411 },
2412 "method": "POST",
2413 "path": "/run",
2414 "status": 200
2415 },
2416 {
2417 "body": {
2418 "text": "order a latte"
2419 },
2420 "expect": {
2421 "inputs": {
2422 "drink": "latte",
2423 "shots": 1
2424 },
2425 "question": "What size of latte?",
2426 "snapshot": {
2427 "inputs": {
2428 "drink": "latte",
2429 "shots": 1
2430 },
2431 "node": "size",
2432 "state": {},
2433 "trace": [
2434 {
2435 "depth": 0,
2436 "elapsed_ms": 0,
2437 "kind": "intent",
2438 "next": "size",
2439 "node": "intent",
2440 "observed": {
2441 "cleaned": "order a latte",
2442 "inputs": {
2443 "drink": "latte"
2444 },
2445 "steps": [],
2446 "trigger": "order a {drink:word}"
2447 },
2448 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
2449 },
2450 {
2451 "depth": 0,
2452 "elapsed_ms": 0,
2453 "kind": "ask",
2454 "next": null,
2455 "node": "size",
2456 "summary": "asked: What size of latte?"
2457 }
2458 ],
2459 "tree": "order"
2460 },
2461 "status": "asked",
2462 "trace": {
2463 "gaps": [],
2464 "steps": [
2465 {
2466 "depth": 0,
2467 "kind": "intent",
2468 "next": "size",
2469 "node": "intent",
2470 "observed": {
2471 "cleaned": "order a latte",
2472 "inputs": {
2473 "drink": "latte"
2474 },
2475 "steps": [],
2476 "trigger": "order a {drink:word}"
2477 },
2478 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
2479 },
2480 {
2481 "depth": 0,
2482 "kind": "ask",
2483 "next": null,
2484 "node": "size",
2485 "summary": "asked: What size of latte?"
2486 }
2487 ],
2488 "tree": "order"
2489 },
2490 "tree": "order"
2491 },
2492 "method": "POST",
2493 "path": "/intent",
2494 "status": 200
2495 },
2496 {
2497 "body": {
2498 "answer": "large",
2499 "snapshot": {
2500 "inputs": {
2501 "drink": "latte",
2502 "shots": 1
2503 },
2504 "node": "size",
2505 "state": {},
2506 "trace": [
2507 {
2508 "depth": 0,
2509 "elapsed_ms": 0,
2510 "kind": "intent",
2511 "next": "size",
2512 "node": "intent",
2513 "observed": {
2514 "cleaned": "order a latte",
2515 "inputs": {
2516 "drink": "latte"
2517 },
2518 "steps": [],
2519 "trigger": "order a {drink:word}"
2520 },
2521 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
2522 },
2523 {
2524 "depth": 0,
2525 "elapsed_ms": 0,
2526 "kind": "ask",
2527 "next": null,
2528 "node": "size",
2529 "summary": "asked: What size of latte?"
2530 }
2531 ],
2532 "tree": "order"
2533 }
2534 },
2535 "expect": {
2536 "inputs": {
2537 "drink": "latte",
2538 "shots": 1
2539 },
2540 "question": "How many shots?",
2541 "snapshot": {
2542 "inputs": {
2543 "drink": "latte",
2544 "shots": 1
2545 },
2546 "node": "shots",
2547 "state": {
2548 "size": "large"
2549 },
2550 "trace": [
2551 {
2552 "depth": 0,
2553 "elapsed_ms": 0,
2554 "kind": "intent",
2555 "next": "size",
2556 "node": "intent",
2557 "observed": {
2558 "cleaned": "order a latte",
2559 "inputs": {
2560 "drink": "latte"
2561 },
2562 "steps": [],
2563 "trigger": "order a {drink:word}"
2564 },
2565 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
2566 },
2567 {
2568 "depth": 0,
2569 "elapsed_ms": 0,
2570 "kind": "ask",
2571 "next": null,
2572 "node": "size",
2573 "summary": "asked: What size of latte?"
2574 },
2575 {
2576 "depth": 0,
2577 "elapsed_ms": 0,
2578 "kind": "ask",
2579 "next": "shots",
2580 "node": "size",
2581 "summary": "answered: large"
2582 },
2583 {
2584 "depth": 0,
2585 "elapsed_ms": 0,
2586 "kind": "ask",
2587 "next": null,
2588 "node": "shots",
2589 "summary": "asked: How many shots?"
2590 }
2591 ],
2592 "tree": "order"
2593 },
2594 "status": "asked",
2595 "trace": {
2596 "gaps": [],
2597 "steps": [
2598 {
2599 "depth": 0,
2600 "kind": "intent",
2601 "next": "size",
2602 "node": "intent",
2603 "observed": {
2604 "cleaned": "order a latte",
2605 "inputs": {
2606 "drink": "latte"
2607 },
2608 "steps": [],
2609 "trigger": "order a {drink:word}"
2610 },
2611 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
2612 },
2613 {
2614 "depth": 0,
2615 "kind": "ask",
2616 "next": null,
2617 "node": "size",
2618 "summary": "asked: What size of latte?"
2619 },
2620 {
2621 "depth": 0,
2622 "kind": "ask",
2623 "next": "shots",
2624 "node": "size",
2625 "summary": "answered: large"
2626 },
2627 {
2628 "depth": 0,
2629 "kind": "ask",
2630 "next": null,
2631 "node": "shots",
2632 "summary": "asked: How many shots?"
2633 }
2634 ],
2635 "tree": "order"
2636 },
2637 "tree": "order"
2638 },
2639 "method": "POST",
2640 "path": "/resume",
2641 "status": 200
2642 },
2643 {
2644 "body": {
2645 "answer": "2",
2646 "snapshot": {
2647 "inputs": {
2648 "drink": "latte",
2649 "shots": 1
2650 },
2651 "node": "shots",
2652 "state": {
2653 "size": "large"
2654 },
2655 "trace": [
2656 {
2657 "depth": 0,
2658 "elapsed_ms": 0,
2659 "kind": "intent",
2660 "next": "size",
2661 "node": "intent",
2662 "observed": {
2663 "cleaned": "order a latte",
2664 "inputs": {
2665 "drink": "latte"
2666 },
2667 "steps": [],
2668 "trigger": "order a {drink:word}"
2669 },
2670 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
2671 },
2672 {
2673 "depth": 0,
2674 "elapsed_ms": 0,
2675 "kind": "ask",
2676 "next": null,
2677 "node": "size",
2678 "summary": "asked: What size of latte?"
2679 },
2680 {
2681 "depth": 0,
2682 "elapsed_ms": 0,
2683 "kind": "ask",
2684 "next": "shots",
2685 "node": "size",
2686 "summary": "answered: large"
2687 },
2688 {
2689 "depth": 0,
2690 "elapsed_ms": 0,
2691 "kind": "ask",
2692 "next": null,
2693 "node": "shots",
2694 "summary": "asked: How many shots?"
2695 }
2696 ],
2697 "tree": "order"
2698 }
2699 },
2700 "expect": {
2701 "inputs": {
2702 "drink": "latte",
2703 "shots": 2
2704 },
2705 "output": {
2706 "shots": 2,
2707 "size": "large"
2708 },
2709 "say": "A large latte, 2 shot(s).",
2710 "status": "done",
2711 "trace": {
2712 "gaps": [],
2713 "steps": [
2714 {
2715 "depth": 0,
2716 "kind": "intent",
2717 "next": "size",
2718 "node": "intent",
2719 "observed": {
2720 "cleaned": "order a latte",
2721 "inputs": {
2722 "drink": "latte"
2723 },
2724 "steps": [],
2725 "trigger": "order a {drink:word}"
2726 },
2727 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
2728 },
2729 {
2730 "depth": 0,
2731 "kind": "ask",
2732 "next": null,
2733 "node": "size",
2734 "summary": "asked: What size of latte?"
2735 },
2736 {
2737 "depth": 0,
2738 "kind": "ask",
2739 "next": "shots",
2740 "node": "size",
2741 "summary": "answered: large"
2742 },
2743 {
2744 "depth": 0,
2745 "kind": "ask",
2746 "next": null,
2747 "node": "shots",
2748 "summary": "asked: How many shots?"
2749 },
2750 {
2751 "depth": 0,
2752 "kind": "ask",
2753 "next": "reply",
2754 "node": "shots",
2755 "summary": "answered: 2"
2756 },
2757 {
2758 "depth": 0,
2759 "kind": "done",
2760 "next": null,
2761 "node": "reply",
2762 "summary": "A large latte, 2 shot(s)."
2763 }
2764 ],
2765 "tree": "order"
2766 },
2767 "tree": "order"
2768 },
2769 "method": "POST",
2770 "path": "/resume",
2771 "status": 200
2772 },
2773 {
2774 "body": {
2775 "snapshot": {
2776 "nope": 1
2777 }
2778 },
2779 "expect": {
2780 "reason": "bad-request",
2781 "say": "Send {\"snapshot\": {...}, \"answer\": ...}.",
2782 "status": "error"
2783 },
2784 "method": "POST",
2785 "path": "/resume",
2786 "status": 200
2787 },
2788 {
2789 "body": {
2790 "text": "order a mocha"
2791 },
2792 "expect": {
2793 "inputs": {
2794 "drink": "mocha",
2795 "shots": 1
2796 },
2797 "question": "What size of mocha?",
2798 "snapshot": {
2799 "inputs": {
2800 "drink": "mocha",
2801 "shots": 1
2802 },
2803 "node": "size",
2804 "state": {},
2805 "trace": [
2806 {
2807 "depth": 0,
2808 "elapsed_ms": 0,
2809 "kind": "intent",
2810 "next": "size",
2811 "node": "intent",
2812 "observed": {
2813 "cleaned": "order a mocha",
2814 "inputs": {
2815 "drink": "mocha"
2816 },
2817 "steps": [],
2818 "trigger": "order a {drink:word}"
2819 },
2820 "summary": "'order a mocha'; order via 'order a {drink:word}' (6 literal chars)"
2821 },
2822 {
2823 "depth": 0,
2824 "elapsed_ms": 0,
2825 "kind": "ask",
2826 "next": null,
2827 "node": "size",
2828 "summary": "asked: What size of mocha?"
2829 }
2830 ],
2831 "tree": "order"
2832 },
2833 "status": "asked",
2834 "trace": {
2835 "gaps": [],
2836 "steps": [
2837 {
2838 "depth": 0,
2839 "kind": "intent",
2840 "next": "size",
2841 "node": "intent",
2842 "observed": {
2843 "cleaned": "order a mocha",
2844 "inputs": {
2845 "drink": "mocha"
2846 },
2847 "steps": [],
2848 "trigger": "order a {drink:word}"
2849 },
2850 "summary": "'order a mocha'; order via 'order a {drink:word}' (6 literal chars)"
2851 },
2852 {
2853 "depth": 0,
2854 "kind": "ask",
2855 "next": null,
2856 "node": "size",
2857 "summary": "asked: What size of mocha?"
2858 }
2859 ],
2860 "tree": "order"
2861 },
2862 "tree": "order"
2863 },
2864 "method": "POST",
2865 "path": "/intent",
2866 "status": 200
2867 },
2868 {
2869 "body": {
2870 "answer": "small",
2871 "snapshot": {
2872 "inputs": {
2873 "drink": "mocha",
2874 "shots": 1
2875 },
2876 "node": "size",
2877 "state": {},
2878 "trace": [
2879 {
2880 "depth": 0,
2881 "elapsed_ms": 0,
2882 "kind": "intent",
2883 "next": "size",
2884 "node": "intent",
2885 "observed": {
2886 "cleaned": "order a mocha",
2887 "inputs": {
2888 "drink": "mocha"
2889 },
2890 "steps": [],
2891 "trigger": "order a {drink:word}"
2892 },
2893 "summary": "'order a mocha'; order via 'order a {drink:word}' (6 literal chars)"
2894 },
2895 {
2896 "depth": 0,
2897 "elapsed_ms": 0,
2898 "kind": "ask",
2899 "next": null,
2900 "node": "size",
2901 "summary": "asked: What size of mocha?"
2902 }
2903 ],
2904 "tree": "order"
2905 }
2906 },
2907 "expect": {
2908 "inputs": {
2909 "drink": "mocha",
2910 "shots": 1
2911 },
2912 "question": "How many shots?",
2913 "snapshot": {
2914 "inputs": {
2915 "drink": "mocha",
2916 "shots": 1
2917 },
2918 "node": "shots",
2919 "state": {
2920 "size": "small"
2921 },
2922 "trace": [
2923 {
2924 "depth": 0,
2925 "elapsed_ms": 0,
2926 "kind": "intent",
2927 "next": "size",
2928 "node": "intent",
2929 "observed": {
2930 "cleaned": "order a mocha",
2931 "inputs": {
2932 "drink": "mocha"
2933 },
2934 "steps": [],
2935 "trigger": "order a {drink:word}"
2936 },
2937 "summary": "'order a mocha'; order via 'order a {drink:word}' (6 literal chars)"
2938 },
2939 {
2940 "depth": 0,
2941 "elapsed_ms": 0,
2942 "kind": "ask",
2943 "next": null,
2944 "node": "size",
2945 "summary": "asked: What size of mocha?"
2946 },
2947 {
2948 "depth": 0,
2949 "elapsed_ms": 0,
2950 "kind": "ask",
2951 "next": "shots",
2952 "node": "size",
2953 "summary": "answered: small"
2954 },
2955 {
2956 "depth": 0,
2957 "elapsed_ms": 0,
2958 "kind": "ask",
2959 "next": null,
2960 "node": "shots",
2961 "summary": "asked: How many shots?"
2962 }
2963 ],
2964 "tree": "order"
2965 },
2966 "status": "asked",
2967 "trace": {
2968 "gaps": [],
2969 "steps": [
2970 {
2971 "depth": 0,
2972 "kind": "intent",
2973 "next": "size",
2974 "node": "intent",
2975 "observed": {
2976 "cleaned": "order a mocha",
2977 "inputs": {
2978 "drink": "mocha"
2979 },
2980 "steps": [],
2981 "trigger": "order a {drink:word}"
2982 },
2983 "summary": "'order a mocha'; order via 'order a {drink:word}' (6 literal chars)"
2984 },
2985 {
2986 "depth": 0,
2987 "kind": "ask",
2988 "next": null,
2989 "node": "size",
2990 "summary": "asked: What size of mocha?"
2991 },
2992 {
2993 "depth": 0,
2994 "kind": "ask",
2995 "next": "shots",
2996 "node": "size",
2997 "summary": "answered: small"
2998 },
2999 {
3000 "depth": 0,
3001 "kind": "ask",
3002 "next": null,
3003 "node": "shots",
3004 "summary": "asked: How many shots?"
3005 }
3006 ],
3007 "tree": "order"
3008 },
3009 "tree": "order"
3010 },
3011 "method": "POST",
3012 "path": "/resume",
3013 "status": 200
3014 },
3015 {
3016 "body": {
3017 "answer": "many",
3018 "snapshot": {
3019 "inputs": {
3020 "drink": "mocha",
3021 "shots": 1
3022 },
3023 "node": "shots",
3024 "state": {
3025 "size": "small"
3026 },
3027 "trace": [
3028 {
3029 "depth": 0,
3030 "elapsed_ms": 0,
3031 "kind": "intent",
3032 "next": "size",
3033 "node": "intent",
3034 "observed": {
3035 "cleaned": "order a mocha",
3036 "inputs": {
3037 "drink": "mocha"
3038 },
3039 "steps": [],
3040 "trigger": "order a {drink:word}"
3041 },
3042 "summary": "'order a mocha'; order via 'order a {drink:word}' (6 literal chars)"
3043 },
3044 {
3045 "depth": 0,
3046 "elapsed_ms": 0,
3047 "kind": "ask",
3048 "next": null,
3049 "node": "size",
3050 "summary": "asked: What size of mocha?"
3051 },
3052 {
3053 "depth": 0,
3054 "elapsed_ms": 0,
3055 "kind": "ask",
3056 "next": "shots",
3057 "node": "size",
3058 "summary": "answered: small"
3059 },
3060 {
3061 "depth": 0,
3062 "elapsed_ms": 0,
3063 "kind": "ask",
3064 "next": null,
3065 "node": "shots",
3066 "summary": "asked: How many shots?"
3067 }
3068 ],
3069 "tree": "order"
3070 }
3071 },
3072 "expect": {
3073 "inputs": {},
3074 "reason": "bad-input",
3075 "say": "input 'shots' must be int, got 'many'",
3076 "status": "error",
3077 "tree": "order"
3078 },
3079 "method": "POST",
3080 "path": "/resume",
3081 "status": 200
3082 },
3083 {
3084 "body": {
3085 "text": "temperature in the hall"
3086 },
3087 "expect": {
3088 "inputs": {
3089 "room": "hall"
3090 },
3091 "output": {
3092 "gap": {
3093 "node": "reading",
3094 "observed": null,
3095 "on": "state.q.json.temperature",
3096 "tree": "room-temperature"
3097 }
3098 },
3099 "reason": "gap",
3100 "say": "reading: no branch for state.q.json.temperature = <missing>.",
3101 "status": "error",
3102 "trace": {
3103 "gaps": [
3104 {
3105 "node": "reading",
3106 "observed": null,
3107 "on": "state.q.json.temperature",
3108 "tree": "room-temperature"
3109 }
3110 ],
3111 "steps": [
3112 {
3113 "depth": 0,
3114 "kind": "intent",
3115 "next": "wait",
3116 "node": "intent",
3117 "observed": {
3118 "cleaned": "temperature in the hall",
3119 "inputs": {
3120 "room": "hall"
3121 },
3122 "steps": [],
3123 "trigger": "[what's the] temperature in the {room:word}"
3124 },
3125 "summary": "'temperature in the hall'; room-temperature via \"[what's the] temperature in the {room:word}\" (25 literal chars)"
3126 },
3127 {
3128 "depth": 0,
3129 "kind": "observe",
3130 "next": "got",
3131 "node": "wait",
3132 "summary": "queue home/hall/temperature at mqtt://broker.local -> 1 message"
3133 },
3134 {
3135 "chosen": "eq True",
3136 "depth": 0,
3137 "kind": "decide",
3138 "next": "reading",
3139 "node": "got",
3140 "observed": true,
3141 "summary": "state.q.received = True"
3142 },
3143 {
3144 "depth": 0,
3145 "kind": "decide",
3146 "next": null,
3147 "node": "reading",
3148 "summary": "state.q.json.temperature = <missing>: no branch"
3149 }
3150 ],
3151 "tree": "room-temperature"
3152 },
3153 "tree": "room-temperature"
3154 },
3155 "method": "POST",
3156 "path": "/intent",
3157 "status": 200
3158 },
3159 {
3160 "body": {
3161 "text": "what's the temperature in the hall"
3162 },
3163 "expect": {
3164 "inputs": {
3165 "room": "hall"
3166 },
3167 "output": {
3168 "gap": {
3169 "node": "reading",
3170 "observed": null,
3171 "on": "state.q.json.temperature",
3172 "tree": "room-temperature"
3173 }
3174 },
3175 "reason": "gap",
3176 "say": "reading: no branch for state.q.json.temperature = <missing>.",
3177 "status": "error",
3178 "trace": {
3179 "gaps": [
3180 {
3181 "node": "reading",
3182 "observed": null,
3183 "on": "state.q.json.temperature",
3184 "tree": "room-temperature"
3185 }
3186 ],
3187 "steps": [
3188 {
3189 "depth": 0,
3190 "kind": "intent",
3191 "next": "wait",
3192 "node": "intent",
3193 "observed": {
3194 "cleaned": "what is the temperature in the hall",
3195 "inputs": {
3196 "room": "hall"
3197 },
3198 "steps": [
3199 "contractions: what's->what is"
3200 ],
3201 "trigger": "[what's the] temperature in the {room:word}"
3202 },
3203 "summary": "\"what's the temperature in the hall\" -> 'what is the temperature in the hall'; room-temperature via \"[what's the] temperature in the {room:word}\" (25 literal chars)"
3204 },
3205 {
3206 "depth": 0,
3207 "kind": "observe",
3208 "next": "got",
3209 "node": "wait",
3210 "summary": "queue home/hall/temperature at mqtt://broker.local -> 1 message"
3211 },
3212 {
3213 "chosen": "eq True",
3214 "depth": 0,
3215 "kind": "decide",
3216 "next": "reading",
3217 "node": "got",
3218 "observed": true,
3219 "summary": "state.q.received = True"
3220 },
3221 {
3222 "depth": 0,
3223 "kind": "decide",
3224 "next": null,
3225 "node": "reading",
3226 "summary": "state.q.json.temperature = <missing>: no branch"
3227 }
3228 ],
3229 "tree": "room-temperature"
3230 },
3231 "tree": "room-temperature"
3232 },
3233 "method": "POST",
3234 "path": "/intent",
3235 "status": 200
3236 },
3237 {
3238 "body": null,
3239 "expect": {
3240 "gaps": [
3241 {
3242 "count": 2,
3243 "node": "reading",
3244 "observed": null,
3245 "on": "state.q.json.temperature",
3246 "tree": "room-temperature"
3247 }
3248 ]
3249 },
3250 "method": "GET",
3251 "path": "/gaps",
3252 "status": 200
3253 },
3254 {
3255 "body": null,
3256 "expect": {
3257 "unanswered": [
3258 {
3259 "count": 2,
3260 "proposal": {
3261 "inputs": {},
3262 "name": "make-sandwich",
3263 "scaffold": "pyrtan new make-sandwich --from \"make me a sandwich\"",
3264 "text": "make me a sandwich",
3265 "trigger": "make me a sandwich"
3266 },
3267 "text": "make me a sandwich"
3268 }
3269 ]
3270 },
3271 "method": "GET",
3272 "path": "/unanswered",
3273 "status": 200
3274 },
3275 {
3276 "body": {
3277 "suggestions": [
3278 {
3279 "by": {
3280 "agent": "scout",
3281 "model": "test"
3282 },
3283 "extra": "dropped",
3284 "kind": "branch",
3285 "meaning": {
3286 "kind": "fail",
3287 "reason": "not-wmo",
3288 "say": "{{ state.name }}: code 100 is not a WMO code."
3289 },
3290 "node": "sky",
3291 "observed": 100,
3292 "on": "code",
3293 "tree": "weather",
3294 "why": "WMO codes stop at 99."
3295 },
3296 {
3297 "covers": [
3298 "is it raining in Oslo"
3299 ],
3300 "kind": "trigger",
3301 "pattern": "is it raining in {place}",
3302 "tree": "weather"
3303 },
3304 {
3305 "description": "Top stories from lobste.rs.",
3306 "hosts_hint": [
3307 "lobste.rs"
3308 ],
3309 "kind": "tree",
3310 "text": "top 3 stories on lobsters"
3311 },
3312 {
3313 "kind": "branch",
3314 "meaning": {
3315 "kind": "done",
3316 "say": "{{ env.SECRET }}"
3317 },
3318 "node": "sky",
3319 "observed": 1,
3320 "tree": "weather"
3321 },
3322 {
3323 "covers": [
3324 "Oslo"
3325 ],
3326 "kind": "trigger",
3327 "pattern": "{place}",
3328 "tree": "weather"
3329 },
3330 {
3331 "kind": "act",
3332 "tree": "weather"
3333 },
3334 "not a table"
3335 ]
3336 },
3337 "expect": {
3338 "kept": 3,
3339 "refused": [
3340 {
3341 "index": 3,
3342 "why": "say may read inputs, state, tree, not env"
3343 },
3344 {
3345 "index": 4,
3346 "why": "pattern: trigger '{place}' has no literal words; it would match anything"
3347 },
3348 {
3349 "index": 5,
3350 "why": "kind is one of branch, trigger, tree"
3351 },
3352 {
3353 "index": 6,
3354 "why": "a suggestion is a table"
3355 }
3356 ],
3357 "status": "ok"
3358 },
3359 "method": "POST",
3360 "path": "/suggestions",
3361 "status": 200
3362 },
3363 {
3364 "body": {
3365 "covers": [
3366 "is it raining in Oslo",
3367 "is it raining in Bergen"
3368 ],
3369 "kind": "trigger",
3370 "pattern": "is it raining in {place}",
3371 "tree": "weather"
3372 },
3373 "expect": {
3374 "kept": 1,
3375 "refused": [],
3376 "status": "ok"
3377 },
3378 "method": "POST",
3379 "path": "/suggestions",
3380 "status": 200
3381 },
3382 {
3383 "body": {
3384 "nothing": true
3385 },
3386 "expect": {
3387 "reason": "bad-request",
3388 "say": "Send {\"suggestions\": [{\"kind\": \"branch\"|\"trigger\"|\"tree\", ...}]}.",
3389 "status": "error"
3390 },
3391 "method": "POST",
3392 "path": "/suggestions",
3393 "status": 400
3394 },
3395 {
3396 "body": null,
3397 "expect": {
3398 "suggestions": [
3399 {
3400 "count": 2,
3401 "covers": [
3402 "is it raining in Oslo",
3403 "is it raining in Bergen"
3404 ],
3405 "kind": "trigger",
3406 "pattern": "is it raining in {place}",
3407 "tree": "weather"
3408 },
3409 {
3410 "by": {
3411 "agent": "scout",
3412 "model": "test"
3413 },
3414 "count": 1,
3415 "kind": "branch",
3416 "meaning": {
3417 "kind": "fail",
3418 "reason": "not-wmo",
3419 "say": "{{ state.name }}: code 100 is not a WMO code."
3420 },
3421 "node": "sky",
3422 "observed": 100,
3423 "on": "code",
3424 "tree": "weather",
3425 "why": "WMO codes stop at 99."
3426 },
3427 {
3428 "count": 1,
3429 "description": "Top stories from lobste.rs.",
3430 "hosts_hint": [
3431 "lobste.rs"
3432 ],
3433 "kind": "tree",
3434 "text": "top 3 stories on lobsters"
3435 }
3436 ]
3437 },
3438 "method": "GET",
3439 "path": "/suggestions",
3440 "status": 200
3441 },
3442 {
3443 "body": null,
3444 "expect": {
3445 "errors": {},
3446 "gaps": 1,
3447 "name": "this browser",
3448 "runs": 19,
3449 "status": "ok",
3450 "suggestions": 3,
3451 "trees": [
3452 "advice",
3453 "air-quality",
3454 "apod",
3455 "books",
3456 "cat-fact",
3457 "count",
3458 "dad-joke",
3459 "define",
3460 "disk-space",
3461 "earthquakes",
3462 "elevation",
3463 "exchange-rate",
3464 "fixed",
3465 "forecast",
3466 "github-release",
3467 "github-repo",
3468 "go-module",
3469 "hacker-news",
3470 "hacker-news-item",
3471 "hn-search",
3472 "holidays",
3473 "homebrew",
3474 "iss",
3475 "library",
3476 "local-time",
3477 "log-errors",
3478 "modem-check",
3479 "my-ip",
3480 "next-launch",
3481 "npm-package",
3482 "order",
3483 "ping",
3484 "pokemon",
3485 "postal-code",
3486 "pypi-package",
3487 "remind",
3488 "rendered-headline",
3489 "rhymes",
3490 "room-temperature",
3491 "site-status",
3492 "small-talk",
3493 "space-news",
3494 "sun",
3495 "synonyms",
3496 "tie-a",
3497 "tie-b",
3498 "uv-index",
3499 "wayback",
3500 "weather",
3501 "wikipedia"
3502 ],
3503 "unanswered": 1,
3504 "watches": 0
3505 },
3506 "method": "GET",
3507 "path": "/health",
3508 "status": 200
3509 },
3510 {
3511 "body": null,
3512 "expect": {
3513 "count": 0,
3514 "watches": [],
3515 "watching": false
3516 },
3517 "method": "GET",
3518 "path": "/watches",
3519 "status": 200
3520 },
3521 {
3522 "body": null,
3523 "expect": {
3524 "reason": "not-found",
3525 "say": "No GET /nowhere here.",
3526 "status": "error"
3527 },
3528 "method": "GET",
3529 "path": "/nowhere",
3530 "status": 404
3531 }
3532 ],
3533 "language_trees": [
3534 {
3535 "description": "echo for remind",
3536 "hosts": [],
3537 "inputs": {
3538 "at": {
3539 "default": "",
3540 "type": "str"
3541 },
3542 "wait": {
3543 "default": 0,
3544 "type": "int"
3545 },
3546 "when": {
3547 "default": "",
3548 "type": "str"
3549 }
3550 },
3551 "name": "remind",
3552 "nodes": {
3553 "reply": {
3554 "kind": "done",
3555 "output": {
3556 "at": "{{ inputs.at }}",
3557 "wait": "{{ inputs.wait }}",
3558 "when": "{{ inputs.when }}"
3559 },
3560 "say": "ok"
3561 }
3562 },
3563 "start": "reply",
3564 "triggers": [
3565 "remind me {when:date} at {at:time}",
3566 "remind me in {wait:duration}"
3567 ],
3568 "version": 1
3569 },
3570 {
3571 "description": "echo for count",
3572 "hosts": [],
3573 "inputs": {
3574 "n": {
3575 "default": 0,
3576 "type": "float"
3577 },
3578 "x": {
3579 "default": 0.0,
3580 "type": "float"
3581 },
3582 "y": {
3583 "default": 0,
3584 "type": "int"
3585 }
3586 },
3587 "name": "count",
3588 "nodes": {
3589 "reply": {
3590 "kind": "done",
3591 "output": {
3592 "n": "{{ inputs.n }}",
3593 "x": "{{ inputs.x }}",
3594 "y": "{{ inputs.y }}"
3595 },
3596 "say": "ok"
3597 }
3598 },
3599 "start": "reply",
3600 "triggers": [
3601 "count to {n:number}",
3602 "add {x:float} and {y:int}"
3603 ],
3604 "version": 1
3605 },
3606 {
3607 "description": "echo for ping",
3608 "hosts": [],
3609 "inputs": {
3610 "host": {
3611 "default": "",
3612 "type": "str"
3613 },
3614 "to": {
3615 "default": "",
3616 "type": "str"
3617 },
3618 "url": {
3619 "default": "",
3620 "type": "str"
3621 }
3622 },
3623 "name": "ping",
3624 "nodes": {
3625 "reply": {
3626 "kind": "done",
3627 "output": {
3628 "host": "{{ inputs.host }}",
3629 "to": "{{ inputs.to }}",
3630 "url": "{{ inputs.url }}"
3631 },
3632 "say": "ok"
3633 }
3634 },
3635 "start": "reply",
3636 "triggers": [
3637 "ping {host:host}",
3638 "fetch {url:url}",
3639 "mail {to:email}"
3640 ],
3641 "version": 1
3642 },
3643 {
3644 "description": "echo for library",
3645 "hosts": [],
3646 "inputs": {
3647 "author": {
3648 "default": "",
3649 "type": "str"
3650 },
3651 "count": {
3652 "default": 3,
3653 "type": "int"
3654 }
3655 },
3656 "language": {
3657 "synonyms": {
3658 "Find": "look up",
3659 "books": [
3660 "novels",
3661 "paperback books"
3662 ]
3663 },
3664 "typos": true
3665 },
3666 "name": "library",
3667 "nodes": {
3668 "reply": {
3669 "kind": "done",
3670 "output": {
3671 "author": "{{ inputs.author }}",
3672 "count": "{{ inputs.count }}"
3673 },
3674 "say": "ok"
3675 }
3676 },
3677 "start": "reply",
3678 "triggers": [
3679 "(find|search) books by {author}",
3680 "top story on book club"
3681 ],
3682 "version": 1
3683 },
3684 {
3685 "description": "echo for tie-a",
3686 "hosts": [],
3687 "inputs": {
3688 "x": {
3689 "default": "",
3690 "type": "str"
3691 }
3692 },
3693 "name": "tie-a",
3694 "nodes": {
3695 "reply": {
3696 "kind": "done",
3697 "output": {
3698 "x": "{{ inputs.x }}"
3699 },
3700 "say": "ok"
3701 }
3702 },
3703 "start": "reply",
3704 "triggers": [
3705 "shout {x:word}"
3706 ],
3707 "version": 1
3708 },
3709 {
3710 "description": "echo for tie-b",
3711 "hosts": [],
3712 "inputs": {
3713 "y": {
3714 "default": "",
3715 "type": "str"
3716 }
3717 },
3718 "name": "tie-b",
3719 "nodes": {
3720 "reply": {
3721 "kind": "done",
3722 "output": {
3723 "y": "{{ inputs.y }}"
3724 },
3725 "say": "ok"
3726 }
3727 },
3728 "start": "reply",
3729 "triggers": [
3730 "shout {y:word}"
3731 ],
3732 "version": 1
3733 },
3734 {
3735 "description": "echo for fixed",
3736 "hosts": [],
3737 "inputs": {
3738 "what": {
3739 "default": "tea",
3740 "type": "str"
3741 }
3742 },
3743 "name": "fixed",
3744 "nodes": {
3745 "reply": {
3746 "kind": "done",
3747 "output": {
3748 "what": "{{ inputs.what }}"
3749 },
3750 "say": "ok"
3751 }
3752 },
3753 "start": "reply",
3754 "triggers": [
3755 {
3756 "inputs": {
3757 "what": "coffee"
3758 },
3759 "pattern": "the usual"
3760 }
3761 ],
3762 "version": 1
3763 },
3764 {
3765 "description": "asks, then answers",
3766 "hosts": [],
3767 "inputs": {
3768 "drink": {
3769 "required": true,
3770 "type": "str"
3771 },
3772 "shots": {
3773 "default": 1,
3774 "type": "int"
3775 }
3776 },
3777 "name": "order",
3778 "nodes": {
3779 "reply": {
3780 "kind": "done",
3781 "output": {
3782 "shots": "{{ inputs.shots }}",
3783 "size": "{{ state.size }}"
3784 },
3785 "say": "A {{ state.size }} {{ inputs.drink }}, {{ inputs.shots }} shot(s)."
3786 },
3787 "shots": {
3788 "into": "inputs.shots",
3789 "kind": "ask",
3790 "next": "reply",
3791 "prompt": "How many shots?"
3792 },
3793 "size": {
3794 "into": "state.size",
3795 "kind": "ask",
3796 "next": "shots",
3797 "prompt": "What size of {{ inputs.drink }}?"
3798 }
3799 },
3800 "start": "size",
3801 "triggers": [
3802 "order a {drink:word}"
3803 ],
3804 "version": 1
3805 }
3806 ],
3807 "nlp": [
3808 {
3809 "fn": "clean",
3810 "text": "Hey, can you please tell me what's up?",
3811 "value": [
3812 "tell me what is up",
3813 [
3814 "fillers: 'Hey', 'can you please'",
3815 "contractions: what's->what is"
3816 ]
3817 ]
3818 },
3819 {
3820 "fn": "clean",
3821 "text": "“Curly” quotes — and dashes",
3822 "value": [
3823 "Curly\" quotes - and dashes",
3824 [
3825 "fold"
3826 ]
3827 ]
3828 },
3829 {
3830 "fn": "clean",
3831 "text": "FULL width",
3832 "value": [
3833 "FULL width",
3834 [
3835 "fold"
3836 ]
3837 ]
3838 },
3839 {
3840 "fn": "clean",
3841 "text": "I'm sure it's a dozen",
3842 "value": [
3843 "I am sure it is 12",
3844 [
3845 "contractions: I'm->i am, it's->it is",
3846 "numbers: a dozen->12"
3847 ]
3848 ]
3849 },
3850 {
3851 "fn": "clean",
3852 "text": "one hundred and twenty three things",
3853 "value": [
3854 "123 things",
3855 [
3856 "numbers: one hundred and twenty three->123"
3857 ]
3858 ]
3859 },
3860 {
3861 "fn": "clean",
3862 "text": "a hundred and one",
3863 "value": [
3864 "101",
3865 [
3866 "numbers: a hundred and one->101"
3867 ]
3868 ]
3869 },
3870 {
3871 "fn": "clean",
3872 "text": "Thanks",
3873 "value": [
3874 "",
3875 [
3876 "fillers: 'Thanks'"
3877 ]
3878 ]
3879 },
3880 {
3881 "fn": "clean",
3882 "text": "well, um, okay pyrtan: weather.",
3883 "value": [
3884 "pyrtan: weather",
3885 [
3886 "fillers: 'well', 'um', 'okay'"
3887 ]
3888 ]
3889 },
3890 {
3891 "fn": "clean",
3892 "text": "St. Ives. Then Mr. Smith",
3893 "value": [
3894 "St Ives Then Mr Smith",
3895 []
3896 ]
3897 },
3898 {
3899 "fn": "clean",
3900 "text": "convert 1,000.5 EUR, then 2, 3",
3901 "value": [
3902 "convert 1,000.5 EUR then 2 3",
3903 []
3904 ]
3905 },
3906 {
3907 "fn": "clean",
3908 "text": "a,b and 1 ,2 and 3, 4",
3909 "value": [
3910 "a b and 1 2 and 3 4",
3911 []
3912 ]
3913 },
3914 {
3915 "fn": "singular",
3916 "text": "stories",
3917 "value": "story"
3918 },
3919 {
3920 "fn": "singular",
3921 "text": "boxes",
3922 "value": "box"
3923 },
3924 {
3925 "fn": "singular",
3926 "text": "news",
3927 "value": "news"
3928 },
3929 {
3930 "fn": "singular",
3931 "text": "glass",
3932 "value": "glass"
3933 },
3934 {
3935 "fn": "singular",
3936 "text": "cats",
3937 "value": "cat"
3938 },
3939 {
3940 "fn": "singular",
3941 "text": "bus",
3942 "value": "bus"
3943 },
3944 {
3945 "fn": "plural",
3946 "text": "story",
3947 "value": "stories"
3948 },
3949 {
3950 "fn": "plural",
3951 "text": "box",
3952 "value": "boxes"
3953 },
3954 {
3955 "fn": "plural",
3956 "text": "day",
3957 "value": "days"
3958 },
3959 {
3960 "fn": "plural",
3961 "text": "status",
3962 "value": "status"
3963 },
3964 {
3965 "fn": "parse_number",
3966 "text": "twenty-one",
3967 "value": 21
3968 },
3969 {
3970 "fn": "parse_number",
3971 "text": "1,234",
3972 "value": 1234
3973 },
3974 {
3975 "fn": "parse_number",
3976 "text": "-3.25",
3977 "value": -3.25
3978 },
3979 {
3980 "error": "'lots' is not a number",
3981 "fn": "parse_number",
3982 "text": "lots"
3983 },
3984 {
3985 "fn": "parse_duration",
3986 "text": "90 seconds",
3987 "value": 90
3988 },
3989 {
3990 "fn": "parse_duration",
3991 "text": "2 days and 3 hours",
3992 "value": 183600
3993 },
3994 {
3995 "fn": "parse_duration",
3996 "text": "a week",
3997 "value": 604800
3998 },
3999 {
4000 "fn": "parse_duration",
4001 "text": "1.5 hours and a half",
4002 "value": 7200
4003 },
4004 {
4005 "error": "'soon' is not a duration",
4006 "fn": "parse_duration",
4007 "text": "soon"
4008 },
4009 {
4010 "error": "'3 fortnights': unknown unit 'fortnights'",
4011 "fn": "parse_duration",
4012 "text": "3 fortnights"
4013 },
4014 {
4015 "fn": "parse_time",
4016 "text": "15:30",
4017 "value": "15:30"
4018 },
4019 {
4020 "fn": "parse_time",
4021 "text": "3:05 pm",
4022 "value": "15:05"
4023 },
4024 {
4025 "fn": "parse_time",
4026 "text": "9 o'clock",
4027 "value": "09:00"
4028 },
4029 {
4030 "fn": "parse_time",
4031 "text": "12am",
4032 "value": "00:00"
4033 },
4034 {
4035 "error": "'13pm': 13 with pm",
4036 "fn": "parse_time",
4037 "text": "13pm"
4038 },
4039 {
4040 "error": "'7': a bare number is not a time; say 15:00 or 3pm",
4041 "fn": "parse_time",
4042 "text": "7"
4043 },
4044 {
4045 "fn": "parse_time",
4046 "text": "midnight",
4047 "value": "00:00"
4048 },
4049 {
4050 "error": "'nine thirty': a bare number is not a time; say 15:00 or 3pm",
4051 "fn": "parse_time",
4052 "text": "nine thirty"
4053 },
4054 {
4055 "error": "'2026-02-29': day is out of range for month",
4056 "fn": "parse_date",
4057 "text": "2026-02-29"
4058 },
4059 {
4060 "fn": "parse_date",
4061 "text": "2028-02-29",
4062 "value": "2028-02-29"
4063 },
4064 {
4065 "fn": "parse_date",
4066 "text": "25 oct",
4067 "value": "2026-10-25"
4068 },
4069 {
4070 "fn": "parse_date",
4071 "text": "oct 25, 2027",
4072 "value": "2027-10-25"
4073 },
4074 {
4075 "fn": "parse_date",
4076 "text": "the 1st of march",
4077 "value": "2026-03-01"
4078 },
4079 {
4080 "fn": "parse_date",
4081 "text": "sept 3",
4082 "value": "2026-09-03"
4083 },
4084 {
4085 "fn": "parse_date",
4086 "text": "25/10/2026",
4087 "value": "2026-10-25"
4088 },
4089 {
4090 "fn": "parse_date",
4091 "text": "10/10",
4092 "value": "2026-10-10"
4093 },
4094 {
4095 "error": "'3/4' could be day/month or month/day; write it as 2026-04-03 or '3 april'",
4096 "fn": "parse_date",
4097 "text": "3/4"
4098 },
4099 {
4100 "fn": "parse_date",
4101 "text": "today",
4102 "value": "2026-10-25"
4103 },
4104 {
4105 "fn": "parse_date",
4106 "text": "yesterday",
4107 "value": "2026-10-24"
4108 },
4109 {
4110 "fn": "parse_date",
4111 "text": "day after tomorrow",
4112 "value": "2026-10-27"
4113 },
4114 {
4115 "fn": "parse_date",
4116 "text": "sunday",
4117 "value": "2026-10-25"
4118 },
4119 {
4120 "fn": "parse_date",
4121 "text": "this sunday",
4122 "value": "2026-10-25"
4123 },
4124 {
4125 "fn": "parse_date",
4126 "text": "next sunday",
4127 "value": "2026-11-01"
4128 },
4129 {
4130 "fn": "parse_date",
4131 "text": "last sunday",
4132 "value": "2026-10-18"
4133 },
4134 {
4135 "fn": "parse_date",
4136 "text": "last mon",
4137 "value": "2026-10-19"
4138 },
4139 {
4140 "fn": "parse_date",
4141 "text": "in 3 weeks",
4142 "value": "2026-11-15"
4143 },
4144 {
4145 "fn": "parse_date",
4146 "text": "in 4 months",
4147 "value": "2027-02-25"
4148 },
4149 {
4150 "fn": "parse_date",
4151 "text": "2 years ago",
4152 "value": "2024-10-25"
4153 },
4154 {
4155 "fn": "parse_date",
4156 "text": "next month",
4157 "value": "2026-11-25"
4158 },
4159 {
4160 "fn": "parse_date",
4161 "text": "end of month",
4162 "value": "2026-10-31"
4163 },
4164 {
4165 "fn": "parse_date",
4166 "text": "end of the year",
4167 "value": "2026-12-31"
4168 },
4169 {
4170 "fn": "parse_date",
4171 "text": "start of month",
4172 "value": "2026-10-01"
4173 },
4174 {
4175 "error": "'someday' is not a date this reads",
4176 "fn": "parse_date",
4177 "text": "someday"
4178 },
4179 {
4180 "error": "'30 feb': day is out of range for month",
4181 "fn": "parse_date",
4182 "text": "30 feb"
4183 },
4184 {
4185 "fn": "parse_host",
4186 "text": "Example.COM",
4187 "value": "example.com"
4188 },
4189 {
4190 "fn": "parse_host",
4191 "text": "https://a.b.example:8443/x",
4192 "value": "a.b.example"
4193 },
4194 {
4195 "error": "'not a host' is not a host name",
4196 "fn": "parse_host",
4197 "text": "not a host"
4198 },
4199 {
4200 "fn": "parse_url",
4201 "text": "http://x.example/y",
4202 "value": "http://x.example/y"
4203 },
4204 {
4205 "error": "'x.example' is not a URL",
4206 "fn": "parse_url",
4207 "text": "x.example"
4208 },
4209 {
4210 "fn": "parse_email",
4211 "text": "A.B@Example.org",
4212 "value": "a.b@example.org"
4213 },
4214 {
4215 "error": "'a@b' is not an email address",
4216 "fn": "parse_email",
4217 "text": "a@b"
4218 }
4219 ],
4220 "proposals": [
4221 {
4222 "expect": {
4223 "inputs": {},
4224 "name": "make-sandwich",
4225 "scaffold": "pyrtan new make-sandwich --from \"make me a sandwich\"",
4226 "text": "make me a sandwich",
4227 "trigger": "make me a sandwich"
4228 },
4229 "text": "make me a sandwich"
4230 },
4231 {
4232 "expect": {
4233 "inputs": {
4234 "host": {
4235 "description": "a host name, as in: example.com",
4236 "role": "host",
4237 "type": "str"
4238 }
4239 },
4240 "name": "host-up",
4241 "scaffold": "pyrtan new host-up --from \"is example.com up\"",
4242 "text": "is example.com up",
4243 "trigger": "is {host:host} up"
4244 },
4245 "text": "is example.com up"
4246 },
4247 {
4248 "expect": {
4249 "inputs": {
4250 "count": {
4251 "description": "as in: 3",
4252 "type": "int"
4253 }
4254 },
4255 "name": "top-stories-lobsters",
4256 "scaffold": "pyrtan new top-stories-lobsters --from \"top 3 stories on lobsters\"",
4257 "text": "top 3 stories on lobsters",
4258 "trigger": "top {count:int} stories on lobsters"
4259 },
4260 "text": "top 3 stories on lobsters"
4261 },
4262 {
4263 "expect": {
4264 "inputs": {
4265 "count": {
4266 "description": "as in: 4",
4267 "type": "int"
4268 },
4269 "value": {
4270 "description": "as in: 2.5",
4271 "type": "float"
4272 }
4273 },
4274 "name": "value-count-times",
4275 "scaffold": "pyrtan new value-count-times --from \"what is 2.5 times 4\"",
4276 "text": "what is 2.5 times 4",
4277 "trigger": "what is {value:float} times {count:int}"
4278 },
4279 "text": "what is 2.5 times 4"
4280 },
4281 {
4282 "expect": {
4283 "inputs": {
4284 "target": {
4285 "description": "as in: foo/bar",
4286 "type": "str"
4287 }
4288 },
4289 "name": "latest-release",
4290 "scaffold": "pyrtan new latest-release --from \"latest release of foo/bar\"",
4291 "text": "latest release of foo/bar",
4292 "trigger": "latest release of {target:word}"
4293 },
4294 "text": "latest release of foo/bar"
4295 },
4296 {
4297 "expect": {
4298 "inputs": {},
4299 "name": "new-tree",
4300 "scaffold": "pyrtan new new-tree --from \"new tree\"",
4301 "text": "new tree",
4302 "trigger": "new tree"
4303 },
4304 "text": ""
4305 },
4306 {
4307 "expect": {
4308 "inputs": {},
4309 "name": "the-the-the",
4310 "scaffold": "pyrtan new the-the-the --from \"the the the\"",
4311 "text": "the the the",
4312 "trigger": "the the the"
4313 },
4314 "text": "the the the"
4315 },
4316 {
4317 "expect": {
4318 "inputs": {},
4319 "name": "weird-pattern-here",
4320 "scaffold": "pyrtan new weird-pattern-here --from \"weird} [pattern] (here)\"",
4321 "text": "weird} [pattern] (here)",
4322 "trigger": "weird pattern here"
4323 },
4324 "text": "{weird} [pattern] (here)"
4325 },
4326 {
4327 "expect": {
4328 "inputs": {},
4329 "name": "weather-mars",
4330 "scaffold": "pyrtan new weather-mars --from \"what's the weather on mars\"",
4331 "text": "what's the weather on mars",
4332 "trigger": "what's the weather on mars"
4333 },
4334 "text": "what's the weather on mars?"
4335 },
4336 {
4337 "expect": {
4338 "inputs": {
4339 "host": {
4340 "description": "a host name, as in: 10.0.0.1",
4341 "role": "host",
4342 "type": "str"
4343 },
4344 "host2": {
4345 "description": "a host name, as in: 10.0.0.2",
4346 "role": "host",
4347 "type": "str"
4348 }
4349 },
4350 "name": "host-host-ping",
4351 "scaffold": "pyrtan new host-host-ping --from \"ping 10.0.0.1 and 10.0.0.2\"",
4352 "text": "ping 10.0.0.1 and 10.0.0.2",
4353 "trigger": "ping {host:host} and {host2:host}"
4354 },
4355 "text": "ping 10.0.0.1 and 10.0.0.2"
4356 },
4357 {
4358 "expect": {
4359 "inputs": {},
4360 "name": "caf-opening-hours",
4361 "scaffold": "pyrtan new caf-opening-hours --from \"café opening hours\"",
4362 "text": "café opening hours",
4363 "trigger": "café opening hours"
4364 },
4365 "text": "café opening hours"
4366 },
4367 {
4368 "expect": {
4369 "inputs": {},
4370 "name": "say-hello-user",
4371 "scaffold": "pyrtan new say-hello-user --from 'say \"hello\" to $USER'",
4372 "text": "say \"hello\" to $USER",
4373 "trigger": "say \"hello\" to $USER"
4374 },
4375 "text": "say \"hello\" to $USER"
4376 },
4377 {
4378 "expect": {
4379 "inputs": {},
4380 "name": "it-s-raining",
4381 "scaffold": "pyrtan new it-s-raining --from \"it's raining\"",
4382 "text": "it's raining",
4383 "trigger": "it's raining"
4384 },
4385 "text": "it's raining"
4386 }
4387 ],
4388 "pyrtan-conformance": 1,
4389 "readings": [
4390 {
4391 "expect": {
4392 "cleaned": "What is the top story on Hacker News",
4393 "considered": [
4394 {
4395 "inputs": {
4396 "count": 1
4397 },
4398 "notes": [],
4399 "rejected": false,
4400 "specificity": 29,
4401 "tree": "hacker-news",
4402 "trigger": "[what's the] top story on hacker news"
4403 }
4404 ],
4405 "matches": [
4406 {
4407 "inputs": {
4408 "count": 1
4409 },
4410 "notes": [],
4411 "rejected": false,
4412 "specificity": 29,
4413 "tree": "hacker-news",
4414 "trigger": "[what's the] top story on hacker news"
4415 }
4416 ],
4417 "now": "2026-10-25T12:00:00+00:00",
4418 "step": {
4419 "kind": "intent",
4420 "node": "intent",
4421 "observed": {
4422 "cleaned": "What is the top story on Hacker News",
4423 "inputs": {
4424 "count": 1
4425 },
4426 "steps": [
4427 "contractions: What's->what is"
4428 ],
4429 "trigger": "[what's the] top story on hacker news"
4430 },
4431 "summary": "\"What's the top story on Hacker News?\" -> 'What is the top story on Hacker News'; hacker-news via \"[what's the] top story on hacker news\" (29 literal chars)"
4432 },
4433 "steps": [
4434 "contractions: What's->what is"
4435 ],
4436 "text": "What's the top story on Hacker News?"
4437 },
4438 "text": "What's the top story on Hacker News?"
4439 },
4440 {
4441 "expect": {
4442 "cleaned": "top 5 stories on hacker news",
4443 "considered": [
4444 {
4445 "inputs": {
4446 "count": 5
4447 },
4448 "notes": [],
4449 "rejected": false,
4450 "specificity": 22,
4451 "tree": "hacker-news",
4452 "trigger": "top {count:int} stories on hacker news"
4453 }
4454 ],
4455 "matches": [
4456 {
4457 "inputs": {
4458 "count": 5
4459 },
4460 "notes": [],
4461 "rejected": false,
4462 "specificity": 22,
4463 "tree": "hacker-news",
4464 "trigger": "top {count:int} stories on hacker news"
4465 }
4466 ],
4467 "now": "2026-10-25T12:00:00+00:00",
4468 "step": {
4469 "kind": "intent",
4470 "node": "intent",
4471 "observed": {
4472 "cleaned": "top 5 stories on hacker news",
4473 "inputs": {
4474 "count": 5
4475 },
4476 "steps": [
4477 "numbers: five->5"
4478 ],
4479 "trigger": "top {count:int} stories on hacker news"
4480 },
4481 "summary": "'top five stories on hacker news' -> 'top 5 stories on hacker news'; hacker-news via 'top {count:int} stories on hacker news' (22 literal chars)"
4482 },
4483 "steps": [
4484 "numbers: five->5"
4485 ],
4486 "text": "top five stories on hacker news"
4487 },
4488 "text": "top five stories on hacker news"
4489 },
4490 {
4491 "expect": {
4492 "cleaned": "top 3 stories on hacker news",
4493 "considered": [
4494 {
4495 "inputs": {
4496 "count": 3
4497 },
4498 "notes": [],
4499 "rejected": false,
4500 "specificity": 22,
4501 "tree": "hacker-news",
4502 "trigger": "top {count:int} stories on hacker news"
4503 }
4504 ],
4505 "matches": [
4506 {
4507 "inputs": {
4508 "count": 3
4509 },
4510 "notes": [],
4511 "rejected": false,
4512 "specificity": 22,
4513 "tree": "hacker-news",
4514 "trigger": "top {count:int} stories on hacker news"
4515 }
4516 ],
4517 "now": "2026-10-25T12:00:00+00:00",
4518 "step": {
4519 "kind": "intent",
4520 "node": "intent",
4521 "observed": {
4522 "cleaned": "top 3 stories on hacker news",
4523 "inputs": {
4524 "count": 3
4525 },
4526 "steps": [
4527 "fillers: 'hey pyrtan', 'please'"
4528 ],
4529 "trigger": "top {count:int} stories on hacker news"
4530 },
4531 "summary": "'hey pyrtan, top 3 stories on hacker news please' -> 'top 3 stories on hacker news'; hacker-news via 'top {count:int} stories on hacker news' (22 literal chars)"
4532 },
4533 "steps": [
4534 "fillers: 'hey pyrtan', 'please'"
4535 ],
4536 "text": "hey pyrtan, top 3 stories on hacker news please"
4537 },
4538 "text": "hey pyrtan, top 3 stories on hacker news please"
4539 },
4540 {
4541 "expect": {
4542 "cleaned": "weather in Oslo",
4543 "considered": [
4544 {
4545 "inputs": {
4546 "place": "Oslo"
4547 },
4548 "notes": [],
4549 "rejected": false,
4550 "specificity": 18,
4551 "tree": "weather",
4552 "trigger": "[what's the] weather in {place}"
4553 }
4554 ],
4555 "matches": [
4556 {
4557 "inputs": {
4558 "place": "Oslo"
4559 },
4560 "notes": [],
4561 "rejected": false,
4562 "specificity": 18,
4563 "tree": "weather",
4564 "trigger": "[what's the] weather in {place}"
4565 }
4566 ],
4567 "now": "2026-10-25T12:00:00+00:00",
4568 "step": {
4569 "kind": "intent",
4570 "node": "intent",
4571 "observed": {
4572 "cleaned": "weather in Oslo",
4573 "inputs": {
4574 "place": "Oslo"
4575 },
4576 "steps": [],
4577 "trigger": "[what's the] weather in {place}"
4578 },
4579 "summary": "'weather in Oslo'; weather via \"[what's the] weather in {place}\" (18 literal chars)"
4580 },
4581 "steps": [],
4582 "text": "weather in Oslo"
4583 },
4584 "text": "weather in Oslo"
4585 },
4586 {
4587 "expect": {
4588 "cleaned": "what is the forecast for New York",
4589 "considered": [
4590 {
4591 "inputs": {
4592 "place": "New York"
4593 },
4594 "notes": [],
4595 "rejected": false,
4596 "specificity": 19,
4597 "tree": "weather",
4598 "trigger": "[what's the] weather for {place}"
4599 }
4600 ],
4601 "matches": [
4602 {
4603 "inputs": {
4604 "place": "New York"
4605 },
4606 "notes": [],
4607 "rejected": false,
4608 "specificity": 19,
4609 "tree": "weather",
4610 "trigger": "[what's the] weather for {place}"
4611 }
4612 ],
4613 "now": "2026-10-25T12:00:00+00:00",
4614 "step": {
4615 "kind": "intent",
4616 "node": "intent",
4617 "observed": {
4618 "cleaned": "what is the forecast for New York",
4619 "inputs": {
4620 "place": "New York"
4621 },
4622 "steps": [
4623 "contractions: what's->what is"
4624 ],
4625 "trigger": "[what's the] weather for {place}"
4626 },
4627 "summary": "\"what's the forecast for New York\" -> 'what is the forecast for New York'; weather via \"[what's the] weather for {place}\" (19 literal chars)"
4628 },
4629 "steps": [
4630 "contractions: what's->what is"
4631 ],
4632 "text": "what's the forecast for New York"
4633 },
4634 "text": "what's the forecast for New York"
4635 },
4636 {
4637 "expect": {
4638 "cleaned": "how cold is it in Oslo",
4639 "considered": [
4640 {
4641 "inputs": {
4642 "place": "Oslo"
4643 },
4644 "notes": [],
4645 "rejected": false,
4646 "specificity": 20,
4647 "tree": "weather",
4648 "trigger": "how [hot|cold|warm] is it in {place}"
4649 }
4650 ],
4651 "matches": [
4652 {
4653 "inputs": {
4654 "place": "Oslo"
4655 },
4656 "notes": [],
4657 "rejected": false,
4658 "specificity": 20,
4659 "tree": "weather",
4660 "trigger": "how [hot|cold|warm] is it in {place}"
4661 }
4662 ],
4663 "now": "2026-10-25T12:00:00+00:00",
4664 "step": {
4665 "kind": "intent",
4666 "node": "intent",
4667 "observed": {
4668 "cleaned": "how cold is it in Oslo",
4669 "inputs": {
4670 "place": "Oslo"
4671 },
4672 "steps": [],
4673 "trigger": "how [hot|cold|warm] is it in {place}"
4674 },
4675 "summary": "'how cold is it in Oslo'; weather via 'how [hot|cold|warm] is it in {place}' (20 literal chars)"
4676 },
4677 "steps": [],
4678 "text": "how cold is it in Oslo"
4679 },
4680 "text": "how cold is it in Oslo"
4681 },
4682 {
4683 "expect": {
4684 "cleaned": "is Example.com up",
4685 "considered": [
4686 {
4687 "inputs": {
4688 "host": "example.com"
4689 },
4690 "notes": [
4691 "host: 'Example.com' read as 'example.com'"
4692 ],
4693 "rejected": false,
4694 "specificity": 4,
4695 "tree": "site-status",
4696 "trigger": "is {host:host} up"
4697 }
4698 ],
4699 "matches": [
4700 {
4701 "inputs": {
4702 "host": "example.com"
4703 },
4704 "notes": [
4705 "host: 'Example.com' read as 'example.com'"
4706 ],
4707 "rejected": false,
4708 "specificity": 4,
4709 "tree": "site-status",
4710 "trigger": "is {host:host} up"
4711 }
4712 ],
4713 "now": "2026-10-25T12:00:00+00:00",
4714 "step": {
4715 "kind": "intent",
4716 "node": "intent",
4717 "observed": {
4718 "cleaned": "is Example.com up",
4719 "inputs": {
4720 "host": "example.com"
4721 },
4722 "notes": [
4723 "host: 'Example.com' read as 'example.com'"
4724 ],
4725 "steps": [],
4726 "trigger": "is {host:host} up"
4727 },
4728 "summary": "'is Example.com up'; site-status via 'is {host:host} up' (4 literal chars); host: 'Example.com' read as 'example.com'"
4729 },
4730 "steps": [],
4731 "text": "is Example.com up"
4732 },
4733 "text": "is Example.com up"
4734 },
4735 {
4736 "expect": {
4737 "cleaned": "is http://Example.com:8080/x up",
4738 "considered": [
4739 {
4740 "inputs": {
4741 "host": "example.com"
4742 },
4743 "notes": [
4744 "host: 'http://Example.com:8080/x' read as 'example.com'"
4745 ],
4746 "rejected": false,
4747 "specificity": 4,
4748 "tree": "site-status",
4749 "trigger": "is {host:host} up"
4750 }
4751 ],
4752 "matches": [
4753 {
4754 "inputs": {
4755 "host": "example.com"
4756 },
4757 "notes": [
4758 "host: 'http://Example.com:8080/x' read as 'example.com'"
4759 ],
4760 "rejected": false,
4761 "specificity": 4,
4762 "tree": "site-status",
4763 "trigger": "is {host:host} up"
4764 }
4765 ],
4766 "now": "2026-10-25T12:00:00+00:00",
4767 "step": {
4768 "kind": "intent",
4769 "node": "intent",
4770 "observed": {
4771 "cleaned": "is http://Example.com:8080/x up",
4772 "inputs": {
4773 "host": "example.com"
4774 },
4775 "notes": [
4776 "host: 'http://Example.com:8080/x' read as 'example.com'"
4777 ],
4778 "steps": [],
4779 "trigger": "is {host:host} up"
4780 },
4781 "summary": "'is http://Example.com:8080/x up'; site-status via 'is {host:host} up' (4 literal chars); host: 'http://Example.com:8080/x' read as 'example.com'"
4782 },
4783 "steps": [],
4784 "text": "is http://Example.com:8080/x up"
4785 },
4786 "text": "is http://Example.com:8080/x up"
4787 },
4788 {
4789 "expect": {
4790 "cleaned": "is not a host up",
4791 "considered": [],
4792 "matches": [],
4793 "now": "2026-10-25T12:00:00+00:00",
4794 "step": {
4795 "kind": "intent",
4796 "node": "intent",
4797 "observed": {
4798 "cleaned": "is not a host up",
4799 "steps": []
4800 },
4801 "summary": "'is not a host up'; no trigger matched"
4802 },
4803 "steps": [],
4804 "text": "is not a host up"
4805 },
4806 "text": "is not a host up"
4807 },
4808 {
4809 "expect": {
4810 "cleaned": "latest release of b-i-t-8/pyrtan",
4811 "considered": [
4812 {
4813 "inputs": {
4814 "repo": "b-i-t-8/pyrtan"
4815 },
4816 "notes": [],
4817 "rejected": false,
4818 "specificity": 24,
4819 "tree": "github-release",
4820 "trigger": "[what's the] latest release of {repo:word}"
4821 }
4822 ],
4823 "matches": [
4824 {
4825 "inputs": {
4826 "repo": "b-i-t-8/pyrtan"
4827 },
4828 "notes": [],
4829 "rejected": false,
4830 "specificity": 24,
4831 "tree": "github-release",
4832 "trigger": "[what's the] latest release of {repo:word}"
4833 }
4834 ],
4835 "now": "2026-10-25T12:00:00+00:00",
4836 "step": {
4837 "kind": "intent",
4838 "node": "intent",
4839 "observed": {
4840 "cleaned": "latest release of b-i-t-8/pyrtan",
4841 "inputs": {
4842 "repo": "b-i-t-8/pyrtan"
4843 },
4844 "steps": [],
4845 "trigger": "[what's the] latest release of {repo:word}"
4846 },
4847 "summary": "'latest release of b-i-t-8/pyrtan'; github-release via \"[what's the] latest release of {repo:word}\" (24 literal chars)"
4848 },
4849 "steps": [],
4850 "text": "latest release of b-i-t-8/pyrtan"
4851 },
4852 "text": "latest release of b-i-t-8/pyrtan"
4853 },
4854 {
4855 "expect": {
4856 "cleaned": "how full is the disk",
4857 "considered": [
4858 {
4859 "inputs": {},
4860 "notes": [],
4861 "rejected": false,
4862 "specificity": 16,
4863 "tree": "disk-space",
4864 "trigger": "[how] full is the disk"
4865 }
4866 ],
4867 "matches": [
4868 {
4869 "inputs": {},
4870 "notes": [],
4871 "rejected": false,
4872 "specificity": 16,
4873 "tree": "disk-space",
4874 "trigger": "[how] full is the disk"
4875 }
4876 ],
4877 "now": "2026-10-25T12:00:00+00:00",
4878 "step": {
4879 "kind": "intent",
4880 "node": "intent",
4881 "observed": {
4882 "cleaned": "how full is the disk",
4883 "inputs": {},
4884 "steps": [],
4885 "trigger": "[how] full is the disk"
4886 },
4887 "summary": "'how full is the disk'; disk-space via '[how] full is the disk' (16 literal chars)"
4888 },
4889 "steps": [],
4890 "text": "how full is the disk"
4891 },
4892 "text": "how full is the disk"
4893 },
4894 {
4895 "expect": {
4896 "cleaned": "disk space on /data",
4897 "considered": [
4898 {
4899 "inputs": {
4900 "mount": "/data"
4901 },
4902 "notes": [],
4903 "rejected": false,
4904 "specificity": 11,
4905 "tree": "disk-space",
4906 "trigger": "disk space on {mount:word}"
4907 }
4908 ],
4909 "matches": [
4910 {
4911 "inputs": {
4912 "mount": "/data"
4913 },
4914 "notes": [],
4915 "rejected": false,
4916 "specificity": 11,
4917 "tree": "disk-space",
4918 "trigger": "disk space on {mount:word}"
4919 }
4920 ],
4921 "now": "2026-10-25T12:00:00+00:00",
4922 "step": {
4923 "kind": "intent",
4924 "node": "intent",
4925 "observed": {
4926 "cleaned": "disk space on /data",
4927 "inputs": {
4928 "mount": "/data"
4929 },
4930 "steps": [],
4931 "trigger": "disk space on {mount:word}"
4932 },
4933 "summary": "'disk space on /data'; disk-space via 'disk space on {mount:word}' (11 literal chars)"
4934 },
4935 "steps": [],
4936 "text": "disk space on /data"
4937 },
4938 "text": "disk space on /data"
4939 },
4940 {
4941 "expect": {
4942 "cleaned": "the temperature in the attic",
4943 "considered": [],
4944 "matches": [],
4945 "now": "2026-10-25T12:00:00+00:00",
4946 "step": {
4947 "kind": "intent",
4948 "node": "intent",
4949 "observed": {
4950 "cleaned": "the temperature in the attic",
4951 "steps": []
4952 },
4953 "summary": "'the temperature in the attic'; no trigger matched"
4954 },
4955 "steps": [],
4956 "text": "the temperature in the attic"
4957 },
4958 "text": "the temperature in the attic"
4959 },
4960 {
4961 "expect": {
4962 "cleaned": "headline of https://app.example/dashboard",
4963 "considered": [
4964 {
4965 "inputs": {
4966 "url": "https://app.example/dashboard"
4967 },
4968 "notes": [],
4969 "rejected": false,
4970 "specificity": 10,
4971 "tree": "rendered-headline",
4972 "trigger": "headline of {url:url}"
4973 }
4974 ],
4975 "matches": [
4976 {
4977 "inputs": {
4978 "url": "https://app.example/dashboard"
4979 },
4980 "notes": [],
4981 "rejected": false,
4982 "specificity": 10,
4983 "tree": "rendered-headline",
4984 "trigger": "headline of {url:url}"
4985 }
4986 ],
4987 "now": "2026-10-25T12:00:00+00:00",
4988 "step": {
4989 "kind": "intent",
4990 "node": "intent",
4991 "observed": {
4992 "cleaned": "headline of https://app.example/dashboard",
4993 "inputs": {
4994 "url": "https://app.example/dashboard"
4995 },
4996 "steps": [],
4997 "trigger": "headline of {url:url}"
4998 },
4999 "summary": "'headline of https://app.example/dashboard'; rendered-headline via 'headline of {url:url}' (10 literal chars)"
5000 },
5001 "steps": [],
5002 "text": "headline of https://app.example/dashboard"
5003 },
5004 "text": "headline of https://app.example/dashboard"
5005 },
5006 {
5007 "expect": {
5008 "cleaned": "is the modem on /dev/ttyUSB1 up",
5009 "considered": [
5010 {
5011 "inputs": {
5012 "device": "/dev/ttyUSB1"
5013 },
5014 "notes": [],
5015 "rejected": false,
5016 "specificity": 14,
5017 "tree": "modem-check",
5018 "trigger": "is the modem on {device:word} up"
5019 }
5020 ],
5021 "matches": [
5022 {
5023 "inputs": {
5024 "device": "/dev/ttyUSB1"
5025 },
5026 "notes": [],
5027 "rejected": false,
5028 "specificity": 14,
5029 "tree": "modem-check",
5030 "trigger": "is the modem on {device:word} up"
5031 }
5032 ],
5033 "now": "2026-10-25T12:00:00+00:00",
5034 "step": {
5035 "kind": "intent",
5036 "node": "intent",
5037 "observed": {
5038 "cleaned": "is the modem on /dev/ttyUSB1 up",
5039 "inputs": {
5040 "device": "/dev/ttyUSB1"
5041 },
5042 "steps": [],
5043 "trigger": "is the modem on {device:word} up"
5044 },
5045 "summary": "'is the modem on /dev/ttyUSB1 up'; modem-check via 'is the modem on {device:word} up' (14 literal chars)"
5046 },
5047 "steps": [],
5048 "text": "is the modem on /dev/ttyUSB1 up"
5049 },
5050 "text": "is the modem on /dev/ttyUSB1 up"
5051 },
5052 {
5053 "expect": {
5054 "cleaned": "make me a sandwich",
5055 "considered": [],
5056 "matches": [],
5057 "now": "2026-10-25T12:00:00+00:00",
5058 "step": {
5059 "kind": "intent",
5060 "node": "intent",
5061 "observed": {
5062 "cleaned": "make me a sandwich",
5063 "steps": []
5064 },
5065 "summary": "'make me a sandwich'; no trigger matched"
5066 },
5067 "steps": [],
5068 "text": "make me a sandwich"
5069 },
5070 "text": "make me a sandwich"
5071 },
5072 {
5073 "expect": {
5074 "cleaned": "",
5075 "considered": [],
5076 "matches": [],
5077 "now": "2026-10-25T12:00:00+00:00",
5078 "step": {
5079 "kind": "intent",
5080 "node": "intent",
5081 "observed": {
5082 "cleaned": "",
5083 "steps": []
5084 },
5085 "summary": "''; no trigger matched"
5086 },
5087 "steps": [],
5088 "text": ""
5089 },
5090 "text": ""
5091 },
5092 {
5093 "expect": {
5094 "cleaned": "",
5095 "considered": [],
5096 "matches": [],
5097 "now": "2026-10-25T12:00:00+00:00",
5098 "step": {
5099 "kind": "intent",
5100 "node": "intent",
5101 "observed": {
5102 "cleaned": "",
5103 "steps": []
5104 },
5105 "summary": "' ?! ' -> ''; no trigger matched"
5106 },
5107 "steps": [],
5108 "text": " ?! "
5109 },
5110 "text": " ?! "
5111 },
5112 {
5113 "expect": {
5114 "cleaned": "tell me the weather in Zürich",
5115 "considered": [],
5116 "matches": [],
5117 "now": "2026-10-25T12:00:00+00:00",
5118 "step": {
5119 "kind": "intent",
5120 "node": "intent",
5121 "observed": {
5122 "cleaned": "tell me the weather in Zürich",
5123 "steps": [
5124 "fillers: 'Could you please', 'thanks'"
5125 ]
5126 },
5127 "summary": "'Could you please tell me the weather in Zürich, thanks!' -> 'tell me the weather in Zürich'; no trigger matched"
5128 },
5129 "steps": [
5130 "fillers: 'Could you please', 'thanks'"
5131 ],
5132 "text": "Could you please tell me the weather in Zürich, thanks!"
5133 },
5134 "text": "Could you please tell me the weather in Zürich, thanks!"
5135 },
5136 {
5137 "expect": {
5138 "cleaned": "TOP 3 STORIES ON HACKER NEWS",
5139 "considered": [
5140 {
5141 "inputs": {
5142 "count": 3
5143 },
5144 "notes": [],
5145 "rejected": false,
5146 "specificity": 22,
5147 "tree": "hacker-news",
5148 "trigger": "top {count:int} stories on hacker news"
5149 }
5150 ],
5151 "matches": [
5152 {
5153 "inputs": {
5154 "count": 3
5155 },
5156 "notes": [],
5157 "rejected": false,
5158 "specificity": 22,
5159 "tree": "hacker-news",
5160 "trigger": "top {count:int} stories on hacker news"
5161 }
5162 ],
5163 "now": "2026-10-25T12:00:00+00:00",
5164 "step": {
5165 "kind": "intent",
5166 "node": "intent",
5167 "observed": {
5168 "cleaned": "TOP 3 STORIES ON HACKER NEWS",
5169 "inputs": {
5170 "count": 3
5171 },
5172 "steps": [],
5173 "trigger": "top {count:int} stories on hacker news"
5174 },
5175 "summary": "'TOP 3 STORIES ON HACKER NEWS'; hacker-news via 'top {count:int} stories on hacker news' (22 literal chars)"
5176 },
5177 "steps": [],
5178 "text": "TOP 3 STORIES ON HACKER NEWS"
5179 },
5180 "text": "TOP 3 STORIES ON HACKER NEWS"
5181 },
5182 {
5183 "expect": {
5184 "cleaned": "top 21 stories on hacker news",
5185 "considered": [
5186 {
5187 "inputs": {
5188 "count": 21
5189 },
5190 "notes": [],
5191 "rejected": false,
5192 "specificity": 22,
5193 "tree": "hacker-news",
5194 "trigger": "top {count:int} stories on hacker news"
5195 }
5196 ],
5197 "matches": [
5198 {
5199 "inputs": {
5200 "count": 21
5201 },
5202 "notes": [],
5203 "rejected": false,
5204 "specificity": 22,
5205 "tree": "hacker-news",
5206 "trigger": "top {count:int} stories on hacker news"
5207 }
5208 ],
5209 "now": "2026-10-25T12:00:00+00:00",
5210 "step": {
5211 "kind": "intent",
5212 "node": "intent",
5213 "observed": {
5214 "cleaned": "top 21 stories on hacker news",
5215 "inputs": {
5216 "count": 21
5217 },
5218 "steps": [
5219 "numbers: twenty-one->21"
5220 ],
5221 "trigger": "top {count:int} stories on hacker news"
5222 },
5223 "summary": "'top twenty-one stories on hacker news' -> 'top 21 stories on hacker news'; hacker-news via 'top {count:int} stories on hacker news' (22 literal chars)"
5224 },
5225 "steps": [
5226 "numbers: twenty-one->21"
5227 ],
5228 "text": "top twenty-one stories on hacker news"
5229 },
5230 "text": "top twenty-one stories on hacker news"
5231 },
5232 {
5233 "expect": {
5234 "cleaned": "weather in Oslo",
5235 "considered": [
5236 {
5237 "inputs": {
5238 "place": "Oslo"
5239 },
5240 "notes": [],
5241 "rejected": false,
5242 "specificity": 18,
5243 "tree": "weather",
5244 "trigger": "[what's the] weather in {place}"
5245 }
5246 ],
5247 "matches": [
5248 {
5249 "inputs": {
5250 "place": "Oslo"
5251 },
5252 "notes": [],
5253 "rejected": false,
5254 "specificity": 18,
5255 "tree": "weather",
5256 "trigger": "[what's the] weather in {place}"
5257 }
5258 ],
5259 "now": "2026-10-25T12:00:00+00:00",
5260 "step": {
5261 "kind": "intent",
5262 "node": "intent",
5263 "observed": {
5264 "cleaned": "weather in Oslo",
5265 "inputs": {
5266 "place": "Oslo"
5267 },
5268 "steps": [
5269 "fillers: 'for me'"
5270 ],
5271 "trigger": "[what's the] weather in {place}"
5272 },
5273 "summary": "'weather in Oslo for me' -> 'weather in Oslo'; weather via \"[what's the] weather in {place}\" (18 literal chars)"
5274 },
5275 "steps": [
5276 "fillers: 'for me'"
5277 ],
5278 "text": "weather in Oslo for me"
5279 },
5280 "text": "weather in Oslo for me"
5281 },
5282 {
5283 "expect": {
5284 "cleaned": "",
5285 "considered": [],
5286 "matches": [],
5287 "now": "2026-10-25T12:00:00+00:00",
5288 "step": {
5289 "kind": "intent",
5290 "node": "intent",
5291 "observed": {
5292 "cleaned": "",
5293 "steps": [
5294 "fillers: 'hello'"
5295 ]
5296 },
5297 "summary": "'hello' -> ''; no trigger matched"
5298 },
5299 "steps": [
5300 "fillers: 'hello'"
5301 ],
5302 "text": "hello"
5303 },
5304 "text": "hello"
5305 },
5306 {
5307 "expect": {
5308 "cleaned": "",
5309 "considered": [],
5310 "matches": [],
5311 "now": "2026-10-25T12:00:00+00:00",
5312 "step": {
5313 "kind": "intent",
5314 "node": "intent",
5315 "observed": {
5316 "cleaned": "",
5317 "steps": [
5318 "fillers: 'pyrtan'"
5319 ]
5320 },
5321 "summary": "'pyrtan' -> ''; no trigger matched"
5322 },
5323 "steps": [
5324 "fillers: 'pyrtan'"
5325 ],
5326 "text": "pyrtan"
5327 },
5328 "text": "pyrtan"
5329 },
5330 {
5331 "expect": {
5332 "cleaned": "remind me tomorrow at 3pm",
5333 "considered": [
5334 {
5335 "inputs": {
5336 "at": "15:00",
5337 "when": "2026-10-26"
5338 },
5339 "notes": [
5340 "when: 'tomorrow' read as '2026-10-26'",
5341 "at: '3pm' read as '15:00'"
5342 ],
5343 "rejected": false,
5344 "specificity": 10,
5345 "tree": "remind",
5346 "trigger": "remind me {when:date} at {at:time}"
5347 }
5348 ],
5349 "matches": [
5350 {
5351 "inputs": {
5352 "at": "15:00",
5353 "when": "2026-10-26"
5354 },
5355 "notes": [
5356 "when: 'tomorrow' read as '2026-10-26'",
5357 "at: '3pm' read as '15:00'"
5358 ],
5359 "rejected": false,
5360 "specificity": 10,
5361 "tree": "remind",
5362 "trigger": "remind me {when:date} at {at:time}"
5363 }
5364 ],
5365 "now": "2026-10-25T12:00:00+00:00",
5366 "step": {
5367 "kind": "intent",
5368 "node": "intent",
5369 "observed": {
5370 "cleaned": "remind me tomorrow at 3pm",
5371 "inputs": {
5372 "at": "15:00",
5373 "when": "2026-10-26"
5374 },
5375 "notes": [
5376 "when: 'tomorrow' read as '2026-10-26'",
5377 "at: '3pm' read as '15:00'"
5378 ],
5379 "steps": [],
5380 "trigger": "remind me {when:date} at {at:time}"
5381 },
5382 "summary": "'remind me tomorrow at 3pm'; remind via 'remind me {when:date} at {at:time}' (10 literal chars); when: 'tomorrow' read as '2026-10-26'; at: '3pm' read as '15:00'"
5383 },
5384 "steps": [],
5385 "text": "remind me tomorrow at 3pm"
5386 },
5387 "text": "remind me tomorrow at 3pm"
5388 },
5389 {
5390 "expect": {
5391 "cleaned": "remind me next friday at noon",
5392 "considered": [
5393 {
5394 "inputs": {
5395 "at": "12:00",
5396 "when": "2026-10-30"
5397 },
5398 "notes": [
5399 "when: 'next friday' read as '2026-10-30'",
5400 "at: 'noon' read as '12:00'"
5401 ],
5402 "rejected": false,
5403 "specificity": 10,
5404 "tree": "remind",
5405 "trigger": "remind me {when:date} at {at:time}"
5406 }
5407 ],
5408 "matches": [
5409 {
5410 "inputs": {
5411 "at": "12:00",
5412 "when": "2026-10-30"
5413 },
5414 "notes": [
5415 "when: 'next friday' read as '2026-10-30'",
5416 "at: 'noon' read as '12:00'"
5417 ],
5418 "rejected": false,
5419 "specificity": 10,
5420 "tree": "remind",
5421 "trigger": "remind me {when:date} at {at:time}"
5422 }
5423 ],
5424 "now": "2026-10-25T12:00:00+00:00",
5425 "step": {
5426 "kind": "intent",
5427 "node": "intent",
5428 "observed": {
5429 "cleaned": "remind me next friday at noon",
5430 "inputs": {
5431 "at": "12:00",
5432 "when": "2026-10-30"
5433 },
5434 "notes": [
5435 "when: 'next friday' read as '2026-10-30'",
5436 "at: 'noon' read as '12:00'"
5437 ],
5438 "steps": [],
5439 "trigger": "remind me {when:date} at {at:time}"
5440 },
5441 "summary": "'remind me next friday at noon'; remind via 'remind me {when:date} at {at:time}' (10 literal chars); when: 'next friday' read as '2026-10-30'; at: 'noon' read as '12:00'"
5442 },
5443 "steps": [],
5444 "text": "remind me next friday at noon"
5445 },
5446 "text": "remind me next friday at noon"
5447 },
5448 {
5449 "expect": {
5450 "cleaned": "remind me 25 dec at 9:30 pm",
5451 "considered": [
5452 {
5453 "inputs": {
5454 "at": "21:30",
5455 "when": "2026-12-25"
5456 },
5457 "notes": [
5458 "when: '25 dec' read as '2026-12-25'",
5459 "at: '9:30 pm' read as '21:30'"
5460 ],
5461 "rejected": false,
5462 "specificity": 10,
5463 "tree": "remind",
5464 "trigger": "remind me {when:date} at {at:time}"
5465 }
5466 ],
5467 "matches": [
5468 {
5469 "inputs": {
5470 "at": "21:30",
5471 "when": "2026-12-25"
5472 },
5473 "notes": [
5474 "when: '25 dec' read as '2026-12-25'",
5475 "at: '9:30 pm' read as '21:30'"
5476 ],
5477 "rejected": false,
5478 "specificity": 10,
5479 "tree": "remind",
5480 "trigger": "remind me {when:date} at {at:time}"
5481 }
5482 ],
5483 "now": "2026-10-25T12:00:00+00:00",
5484 "step": {
5485 "kind": "intent",
5486 "node": "intent",
5487 "observed": {
5488 "cleaned": "remind me 25 dec at 9:30 pm",
5489 "inputs": {
5490 "at": "21:30",
5491 "when": "2026-12-25"
5492 },
5493 "notes": [
5494 "when: '25 dec' read as '2026-12-25'",
5495 "at: '9:30 pm' read as '21:30'"
5496 ],
5497 "steps": [],
5498 "trigger": "remind me {when:date} at {at:time}"
5499 },
5500 "summary": "'remind me 25 dec at 9:30 pm'; remind via 'remind me {when:date} at {at:time}' (10 literal chars); when: '25 dec' read as '2026-12-25'; at: '9:30 pm' read as '21:30'"
5501 },
5502 "steps": [],
5503 "text": "remind me 25 dec at 9:30 pm"
5504 },
5505 "text": "remind me 25 dec at 9:30 pm"
5506 },
5507 {
5508 "expect": {
5509 "cleaned": "remind me 3/4 at 10am",
5510 "considered": [
5511 {
5512 "inputs": {},
5513 "notes": [
5514 "slot when: '3/4' could be day/month or month/day; write it as 2026-04-03 or '3 april'"
5515 ],
5516 "rejected": true,
5517 "specificity": 10,
5518 "tree": "remind",
5519 "trigger": "remind me {when:date} at {at:time}"
5520 }
5521 ],
5522 "matches": [],
5523 "now": "2026-10-25T12:00:00+00:00",
5524 "step": {
5525 "kind": "intent",
5526 "node": "intent",
5527 "observed": {
5528 "cleaned": "remind me 3/4 at 10am",
5529 "steps": []
5530 },
5531 "summary": "'remind me 3/4 at 10am'; no trigger matched"
5532 },
5533 "steps": [],
5534 "text": "remind me 3/4 at 10am"
5535 },
5536 "text": "remind me 3/4 at 10am"
5537 },
5538 {
5539 "expect": {
5540 "cleaned": "remind me 31/12 at 25:00",
5541 "considered": [
5542 {
5543 "inputs": {},
5544 "notes": [
5545 "when: '31/12' read as '2026-12-31'",
5546 "slot at: '25:00' is not a time"
5547 ],
5548 "rejected": true,
5549 "specificity": 10,
5550 "tree": "remind",
5551 "trigger": "remind me {when:date} at {at:time}"
5552 }
5553 ],
5554 "matches": [],
5555 "now": "2026-10-25T12:00:00+00:00",
5556 "step": {
5557 "kind": "intent",
5558 "node": "intent",
5559 "observed": {
5560 "cleaned": "remind me 31/12 at 25:00",
5561 "steps": []
5562 },
5563 "summary": "'remind me 31/12 at 25:00'; no trigger matched"
5564 },
5565 "steps": [],
5566 "text": "remind me 31/12 at 25:00"
5567 },
5568 "text": "remind me 31/12 at 25:00"
5569 },
5570 {
5571 "expect": {
5572 "cleaned": "remind me in 2 hours",
5573 "considered": [
5574 {
5575 "inputs": {
5576 "wait": 7200
5577 },
5578 "notes": [
5579 "wait: '2 hours' read as 7200"
5580 ],
5581 "rejected": false,
5582 "specificity": 10,
5583 "tree": "remind",
5584 "trigger": "remind me in {wait:duration}"
5585 }
5586 ],
5587 "matches": [
5588 {
5589 "inputs": {
5590 "wait": 7200
5591 },
5592 "notes": [
5593 "wait: '2 hours' read as 7200"
5594 ],
5595 "rejected": false,
5596 "specificity": 10,
5597 "tree": "remind",
5598 "trigger": "remind me in {wait:duration}"
5599 }
5600 ],
5601 "now": "2026-10-25T12:00:00+00:00",
5602 "step": {
5603 "kind": "intent",
5604 "node": "intent",
5605 "observed": {
5606 "cleaned": "remind me in 2 hours",
5607 "inputs": {
5608 "wait": 7200
5609 },
5610 "notes": [
5611 "wait: '2 hours' read as 7200"
5612 ],
5613 "steps": [],
5614 "trigger": "remind me in {wait:duration}"
5615 },
5616 "summary": "'remind me in 2 hours'; remind via 'remind me in {wait:duration}' (10 literal chars); wait: '2 hours' read as 7200"
5617 },
5618 "steps": [],
5619 "text": "remind me in 2 hours"
5620 },
5621 "text": "remind me in 2 hours"
5622 },
5623 {
5624 "expect": {
5625 "cleaned": "remind me in an hour and a half",
5626 "considered": [
5627 {
5628 "inputs": {
5629 "wait": 5400
5630 },
5631 "notes": [
5632 "wait: 'an hour and a half' read as 5400"
5633 ],
5634 "rejected": false,
5635 "specificity": 10,
5636 "tree": "remind",
5637 "trigger": "remind me in {wait:duration}"
5638 }
5639 ],
5640 "matches": [
5641 {
5642 "inputs": {
5643 "wait": 5400
5644 },
5645 "notes": [
5646 "wait: 'an hour and a half' read as 5400"
5647 ],
5648 "rejected": false,
5649 "specificity": 10,
5650 "tree": "remind",
5651 "trigger": "remind me in {wait:duration}"
5652 }
5653 ],
5654 "now": "2026-10-25T12:00:00+00:00",
5655 "step": {
5656 "kind": "intent",
5657 "node": "intent",
5658 "observed": {
5659 "cleaned": "remind me in an hour and a half",
5660 "inputs": {
5661 "wait": 5400
5662 },
5663 "notes": [
5664 "wait: 'an hour and a half' read as 5400"
5665 ],
5666 "steps": [],
5667 "trigger": "remind me in {wait:duration}"
5668 },
5669 "summary": "'remind me in an hour and a half'; remind via 'remind me in {wait:duration}' (10 literal chars); wait: 'an hour and a half' read as 5400"
5670 },
5671 "steps": [],
5672 "text": "remind me in an hour and a half"
5673 },
5674 "text": "remind me in an hour and a half"
5675 },
5676 {
5677 "expect": {
5678 "cleaned": "remind me in 1h30m",
5679 "considered": [
5680 {
5681 "inputs": {
5682 "wait": 5400
5683 },
5684 "notes": [
5685 "wait: '1h30m' read as 5400"
5686 ],
5687 "rejected": false,
5688 "specificity": 10,
5689 "tree": "remind",
5690 "trigger": "remind me in {wait:duration}"
5691 }
5692 ],
5693 "matches": [
5694 {
5695 "inputs": {
5696 "wait": 5400
5697 },
5698 "notes": [
5699 "wait: '1h30m' read as 5400"
5700 ],
5701 "rejected": false,
5702 "specificity": 10,
5703 "tree": "remind",
5704 "trigger": "remind me in {wait:duration}"
5705 }
5706 ],
5707 "now": "2026-10-25T12:00:00+00:00",
5708 "step": {
5709 "kind": "intent",
5710 "node": "intent",
5711 "observed": {
5712 "cleaned": "remind me in 1h30m",
5713 "inputs": {
5714 "wait": 5400
5715 },
5716 "notes": [
5717 "wait: '1h30m' read as 5400"
5718 ],
5719 "steps": [],
5720 "trigger": "remind me in {wait:duration}"
5721 },
5722 "summary": "'remind me in 1h30m'; remind via 'remind me in {wait:duration}' (10 literal chars); wait: '1h30m' read as 5400"
5723 },
5724 "steps": [],
5725 "text": "remind me in 1h30m"
5726 },
5727 "text": "remind me in 1h30m"
5728 },
5729 {
5730 "expect": {
5731 "cleaned": "remind me in half an hour",
5732 "considered": [
5733 {
5734 "inputs": {
5735 "wait": 1800
5736 },
5737 "notes": [
5738 "wait: 'half an hour' read as 1800"
5739 ],
5740 "rejected": false,
5741 "specificity": 10,
5742 "tree": "remind",
5743 "trigger": "remind me in {wait:duration}"
5744 }
5745 ],
5746 "matches": [
5747 {
5748 "inputs": {
5749 "wait": 1800
5750 },
5751 "notes": [
5752 "wait: 'half an hour' read as 1800"
5753 ],
5754 "rejected": false,
5755 "specificity": 10,
5756 "tree": "remind",
5757 "trigger": "remind me in {wait:duration}"
5758 }
5759 ],
5760 "now": "2026-10-25T12:00:00+00:00",
5761 "step": {
5762 "kind": "intent",
5763 "node": "intent",
5764 "observed": {
5765 "cleaned": "remind me in half an hour",
5766 "inputs": {
5767 "wait": 1800
5768 },
5769 "notes": [
5770 "wait: 'half an hour' read as 1800"
5771 ],
5772 "steps": [],
5773 "trigger": "remind me in {wait:duration}"
5774 },
5775 "summary": "'remind me in half an hour'; remind via 'remind me in {wait:duration}' (10 literal chars); wait: 'half an hour' read as 1800"
5776 },
5777 "steps": [],
5778 "text": "remind me in half an hour"
5779 },
5780 "text": "remind me in half an hour"
5781 },
5782 {
5783 "expect": {
5784 "cleaned": "remind me in 2 parsecs",
5785 "considered": [
5786 {
5787 "inputs": {},
5788 "notes": [
5789 "slot wait: '2 parsecs': unknown unit 'parsecs'"
5790 ],
5791 "rejected": true,
5792 "specificity": 10,
5793 "tree": "remind",
5794 "trigger": "remind me in {wait:duration}"
5795 }
5796 ],
5797 "matches": [],
5798 "now": "2026-10-25T12:00:00+00:00",
5799 "step": {
5800 "kind": "intent",
5801 "node": "intent",
5802 "observed": {
5803 "cleaned": "remind me in 2 parsecs",
5804 "steps": []
5805 },
5806 "summary": "'remind me in 2 parsecs'; no trigger matched"
5807 },
5808 "steps": [],
5809 "text": "remind me in 2 parsecs"
5810 },
5811 "text": "remind me in 2 parsecs"
5812 },
5813 {
5814 "expect": {
5815 "cleaned": "count to 205",
5816 "considered": [
5817 {
5818 "inputs": {
5819 "n": 205
5820 },
5821 "notes": [],
5822 "rejected": false,
5823 "specificity": 7,
5824 "tree": "count",
5825 "trigger": "count to {n:number}"
5826 }
5827 ],
5828 "matches": [
5829 {
5830 "inputs": {
5831 "n": 205
5832 },
5833 "notes": [],
5834 "rejected": false,
5835 "specificity": 7,
5836 "tree": "count",
5837 "trigger": "count to {n:number}"
5838 }
5839 ],
5840 "now": "2026-10-25T12:00:00+00:00",
5841 "step": {
5842 "kind": "intent",
5843 "node": "intent",
5844 "observed": {
5845 "cleaned": "count to 205",
5846 "inputs": {
5847 "n": 205
5848 },
5849 "steps": [
5850 "numbers: two hundred and five->205"
5851 ],
5852 "trigger": "count to {n:number}"
5853 },
5854 "summary": "'count to two hundred and five' -> 'count to 205'; count via 'count to {n:number}' (7 literal chars)"
5855 },
5856 "steps": [
5857 "numbers: two hundred and five->205"
5858 ],
5859 "text": "count to two hundred and five"
5860 },
5861 "text": "count to two hundred and five"
5862 },
5863 {
5864 "expect": {
5865 "cleaned": "count to 1,000.5",
5866 "considered": [
5867 {
5868 "inputs": {
5869 "n": 1000.5
5870 },
5871 "notes": [
5872 "n: '1,000.5' read as 1000.5"
5873 ],
5874 "rejected": false,
5875 "specificity": 7,
5876 "tree": "count",
5877 "trigger": "count to {n:number}"
5878 }
5879 ],
5880 "matches": [
5881 {
5882 "inputs": {
5883 "n": 1000.5
5884 },
5885 "notes": [
5886 "n: '1,000.5' read as 1000.5"
5887 ],
5888 "rejected": false,
5889 "specificity": 7,
5890 "tree": "count",
5891 "trigger": "count to {n:number}"
5892 }
5893 ],
5894 "now": "2026-10-25T12:00:00+00:00",
5895 "step": {
5896 "kind": "intent",
5897 "node": "intent",
5898 "observed": {
5899 "cleaned": "count to 1,000.5",
5900 "inputs": {
5901 "n": 1000.5
5902 },
5903 "notes": [
5904 "n: '1,000.5' read as 1000.5"
5905 ],
5906 "steps": [],
5907 "trigger": "count to {n:number}"
5908 },
5909 "summary": "'count to 1,000.5'; count via 'count to {n:number}' (7 literal chars); n: '1,000.5' read as 1000.5"
5910 },
5911 "steps": [],
5912 "text": "count to 1,000.5"
5913 },
5914 "text": "count to 1,000.5"
5915 },
5916 {
5917 "expect": {
5918 "cleaned": "count to 2.0",
5919 "considered": [
5920 {
5921 "inputs": {
5922 "n": 2.0
5923 },
5924 "notes": [],
5925 "rejected": false,
5926 "specificity": 7,
5927 "tree": "count",
5928 "trigger": "count to {n:number}"
5929 }
5930 ],
5931 "matches": [
5932 {
5933 "inputs": {
5934 "n": 2.0
5935 },
5936 "notes": [],
5937 "rejected": false,
5938 "specificity": 7,
5939 "tree": "count",
5940 "trigger": "count to {n:number}"
5941 }
5942 ],
5943 "now": "2026-10-25T12:00:00+00:00",
5944 "step": {
5945 "kind": "intent",
5946 "node": "intent",
5947 "observed": {
5948 "cleaned": "count to 2.0",
5949 "inputs": {
5950 "n": 2.0
5951 },
5952 "steps": [],
5953 "trigger": "count to {n:number}"
5954 },
5955 "summary": "'count to 2.0'; count via 'count to {n:number}' (7 literal chars)"
5956 },
5957 "steps": [],
5958 "text": "count to 2.0"
5959 },
5960 "text": "count to 2.0"
5961 },
5962 {
5963 "expect": {
5964 "cleaned": "count to 12",
5965 "considered": [
5966 {
5967 "inputs": {
5968 "n": 12
5969 },
5970 "notes": [],
5971 "rejected": false,
5972 "specificity": 7,
5973 "tree": "count",
5974 "trigger": "count to {n:number}"
5975 }
5976 ],
5977 "matches": [
5978 {
5979 "inputs": {
5980 "n": 12
5981 },
5982 "notes": [],
5983 "rejected": false,
5984 "specificity": 7,
5985 "tree": "count",
5986 "trigger": "count to {n:number}"
5987 }
5988 ],
5989 "now": "2026-10-25T12:00:00+00:00",
5990 "step": {
5991 "kind": "intent",
5992 "node": "intent",
5993 "observed": {
5994 "cleaned": "count to 12",
5995 "inputs": {
5996 "n": 12
5997 },
5998 "steps": [
5999 "numbers: a dozen->12"
6000 ],
6001 "trigger": "count to {n:number}"
6002 },
6003 "summary": "'count to a dozen' -> 'count to 12'; count via 'count to {n:number}' (7 literal chars)"
6004 },
6005 "steps": [
6006 "numbers: a dozen->12"
6007 ],
6008 "text": "count to a dozen"
6009 },
6010 "text": "count to a dozen"
6011 },
6012 {
6013 "expect": {
6014 "cleaned": "add 1.5 and 3",
6015 "considered": [
6016 {
6017 "inputs": {
6018 "x": 1.5,
6019 "y": 3
6020 },
6021 "notes": [],
6022 "rejected": false,
6023 "specificity": 6,
6024 "tree": "count",
6025 "trigger": "add {x:float} and {y:int}"
6026 }
6027 ],
6028 "matches": [
6029 {
6030 "inputs": {
6031 "x": 1.5,
6032 "y": 3
6033 },
6034 "notes": [],
6035 "rejected": false,
6036 "specificity": 6,
6037 "tree": "count",
6038 "trigger": "add {x:float} and {y:int}"
6039 }
6040 ],
6041 "now": "2026-10-25T12:00:00+00:00",
6042 "step": {
6043 "kind": "intent",
6044 "node": "intent",
6045 "observed": {
6046 "cleaned": "add 1.5 and 3",
6047 "inputs": {
6048 "x": 1.5,
6049 "y": 3
6050 },
6051 "steps": [],
6052 "trigger": "add {x:float} and {y:int}"
6053 },
6054 "summary": "'add 1.5 and 3'; count via 'add {x:float} and {y:int}' (6 literal chars)"
6055 },
6056 "steps": [],
6057 "text": "add 1.5 and 3"
6058 },
6059 "text": "add 1.5 and 3"
6060 },
6061 {
6062 "expect": {
6063 "cleaned": "ping localhost",
6064 "considered": [
6065 {
6066 "inputs": {
6067 "host": "localhost"
6068 },
6069 "notes": [],
6070 "rejected": false,
6071 "specificity": 4,
6072 "tree": "ping",
6073 "trigger": "ping {host:host}"
6074 }
6075 ],
6076 "matches": [
6077 {
6078 "inputs": {
6079 "host": "localhost"
6080 },
6081 "notes": [],
6082 "rejected": false,
6083 "specificity": 4,
6084 "tree": "ping",
6085 "trigger": "ping {host:host}"
6086 }
6087 ],
6088 "now": "2026-10-25T12:00:00+00:00",
6089 "step": {
6090 "kind": "intent",
6091 "node": "intent",
6092 "observed": {
6093 "cleaned": "ping localhost",
6094 "inputs": {
6095 "host": "localhost"
6096 },
6097 "steps": [],
6098 "trigger": "ping {host:host}"
6099 },
6100 "summary": "'ping localhost'; ping via 'ping {host:host}' (4 literal chars)"
6101 },
6102 "steps": [],
6103 "text": "ping localhost"
6104 },
6105 "text": "ping localhost"
6106 },
6107 {
6108 "expect": {
6109 "cleaned": "ping 10.0.0.1:22",
6110 "considered": [
6111 {
6112 "inputs": {
6113 "host": "10.0.0.1"
6114 },
6115 "notes": [
6116 "host: '10.0.0.1:22' read as '10.0.0.1'"
6117 ],
6118 "rejected": false,
6119 "specificity": 4,
6120 "tree": "ping",
6121 "trigger": "ping {host:host}"
6122 }
6123 ],
6124 "matches": [
6125 {
6126 "inputs": {
6127 "host": "10.0.0.1"
6128 },
6129 "notes": [
6130 "host: '10.0.0.1:22' read as '10.0.0.1'"
6131 ],
6132 "rejected": false,
6133 "specificity": 4,
6134 "tree": "ping",
6135 "trigger": "ping {host:host}"
6136 }
6137 ],
6138 "now": "2026-10-25T12:00:00+00:00",
6139 "step": {
6140 "kind": "intent",
6141 "node": "intent",
6142 "observed": {
6143 "cleaned": "ping 10.0.0.1:22",
6144 "inputs": {
6145 "host": "10.0.0.1"
6146 },
6147 "notes": [
6148 "host: '10.0.0.1:22' read as '10.0.0.1'"
6149 ],
6150 "steps": [],
6151 "trigger": "ping {host:host}"
6152 },
6153 "summary": "'ping 10.0.0.1:22'; ping via 'ping {host:host}' (4 literal chars); host: '10.0.0.1:22' read as '10.0.0.1'"
6154 },
6155 "steps": [],
6156 "text": "ping 10.0.0.1:22"
6157 },
6158 "text": "ping 10.0.0.1:22"
6159 },
6160 {
6161 "expect": {
6162 "cleaned": "ping not_a_host",
6163 "considered": [
6164 {
6165 "inputs": {},
6166 "notes": [
6167 "slot host: 'not_a_host' is not a host name"
6168 ],
6169 "rejected": true,
6170 "specificity": 4,
6171 "tree": "ping",
6172 "trigger": "ping {host:host}"
6173 }
6174 ],
6175 "matches": [],
6176 "now": "2026-10-25T12:00:00+00:00",
6177 "step": {
6178 "kind": "intent",
6179 "node": "intent",
6180 "observed": {
6181 "cleaned": "ping not_a_host",
6182 "steps": []
6183 },
6184 "summary": "'ping not_a_host'; no trigger matched"
6185 },
6186 "steps": [],
6187 "text": "ping not_a_host"
6188 },
6189 "text": "ping not_a_host"
6190 },
6191 {
6192 "expect": {
6193 "cleaned": "fetch https://example.com/a b=c",
6194 "considered": [],
6195 "matches": [],
6196 "now": "2026-10-25T12:00:00+00:00",
6197 "step": {
6198 "kind": "intent",
6199 "node": "intent",
6200 "observed": {
6201 "cleaned": "fetch https://example.com/a b=c",
6202 "steps": []
6203 },
6204 "summary": "'fetch https://example.com/a?b=c' -> 'fetch https://example.com/a b=c'; no trigger matched"
6205 },
6206 "steps": [],
6207 "text": "fetch https://example.com/a?b=c"
6208 },
6209 "text": "fetch https://example.com/a?b=c"
6210 },
6211 {
6212 "expect": {
6213 "cleaned": "fetch ftp://x",
6214 "considered": [
6215 {
6216 "inputs": {},
6217 "notes": [
6218 "slot url: 'ftp://x' is not a URL"
6219 ],
6220 "rejected": true,
6221 "specificity": 5,
6222 "tree": "ping",
6223 "trigger": "fetch {url:url}"
6224 }
6225 ],
6226 "matches": [],
6227 "now": "2026-10-25T12:00:00+00:00",
6228 "step": {
6229 "kind": "intent",
6230 "node": "intent",
6231 "observed": {
6232 "cleaned": "fetch ftp://x",
6233 "steps": []
6234 },
6235 "summary": "'fetch ftp://x'; no trigger matched"
6236 },
6237 "steps": [],
6238 "text": "fetch ftp://x"
6239 },
6240 "text": "fetch ftp://x"
6241 },
6242 {
6243 "expect": {
6244 "cleaned": "mail Barry@Example.COM",
6245 "considered": [
6246 {
6247 "inputs": {
6248 "to": "barry@example.com"
6249 },
6250 "notes": [
6251 "to: 'Barry@Example.COM' read as 'barry@example.com'"
6252 ],
6253 "rejected": false,
6254 "specificity": 4,
6255 "tree": "ping",
6256 "trigger": "mail {to:email}"
6257 }
6258 ],
6259 "matches": [
6260 {
6261 "inputs": {
6262 "to": "barry@example.com"
6263 },
6264 "notes": [
6265 "to: 'Barry@Example.COM' read as 'barry@example.com'"
6266 ],
6267 "rejected": false,
6268 "specificity": 4,
6269 "tree": "ping",
6270 "trigger": "mail {to:email}"
6271 }
6272 ],
6273 "now": "2026-10-25T12:00:00+00:00",
6274 "step": {
6275 "kind": "intent",
6276 "node": "intent",
6277 "observed": {
6278 "cleaned": "mail Barry@Example.COM",
6279 "inputs": {
6280 "to": "barry@example.com"
6281 },
6282 "notes": [
6283 "to: 'Barry@Example.COM' read as 'barry@example.com'"
6284 ],
6285 "steps": [],
6286 "trigger": "mail {to:email}"
6287 },
6288 "summary": "'mail Barry@Example.COM'; ping via 'mail {to:email}' (4 literal chars); to: 'Barry@Example.COM' read as 'barry@example.com'"
6289 },
6290 "steps": [],
6291 "text": "mail Barry@Example.COM"
6292 },
6293 "text": "mail Barry@Example.COM"
6294 },
6295 {
6296 "expect": {
6297 "cleaned": "mail nobody",
6298 "considered": [
6299 {
6300 "inputs": {},
6301 "notes": [
6302 "slot to: 'nobody' is not an email address"
6303 ],
6304 "rejected": true,
6305 "specificity": 4,
6306 "tree": "ping",
6307 "trigger": "mail {to:email}"
6308 }
6309 ],
6310 "matches": [],
6311 "now": "2026-10-25T12:00:00+00:00",
6312 "step": {
6313 "kind": "intent",
6314 "node": "intent",
6315 "observed": {
6316 "cleaned": "mail nobody",
6317 "steps": []
6318 },
6319 "summary": "'mail nobody'; no trigger matched"
6320 },
6321 "steps": [],
6322 "text": "mail nobody"
6323 },
6324 "text": "mail nobody"
6325 },
6326 {
6327 "expect": {
6328 "cleaned": "find books by Ursula Le Guin",
6329 "considered": [
6330 {
6331 "inputs": {
6332 "author": "Ursula Le Guin"
6333 },
6334 "notes": [],
6335 "rejected": false,
6336 "specificity": 17,
6337 "tree": "library",
6338 "trigger": "(find|search) books by {author}"
6339 }
6340 ],
6341 "matches": [
6342 {
6343 "inputs": {
6344 "author": "Ursula Le Guin"
6345 },
6346 "notes": [],
6347 "rejected": false,
6348 "specificity": 17,
6349 "tree": "library",
6350 "trigger": "(find|search) books by {author}"
6351 }
6352 ],
6353 "now": "2026-10-25T12:00:00+00:00",
6354 "step": {
6355 "kind": "intent",
6356 "node": "intent",
6357 "observed": {
6358 "cleaned": "find books by Ursula Le Guin",
6359 "inputs": {
6360 "author": "Ursula Le Guin"
6361 },
6362 "steps": [],
6363 "trigger": "(find|search) books by {author}"
6364 },
6365 "summary": "'find books by Ursula Le Guin'; library via '(find|search) books by {author}' (17 literal chars)"
6366 },
6367 "steps": [],
6368 "text": "find books by Ursula Le Guin"
6369 },
6370 "text": "find books by Ursula Le Guin"
6371 },
6372 {
6373 "expect": {
6374 "cleaned": "search novels by Le Guin",
6375 "considered": [
6376 {
6377 "inputs": {
6378 "author": "Le Guin"
6379 },
6380 "notes": [],
6381 "rejected": false,
6382 "specificity": 17,
6383 "tree": "library",
6384 "trigger": "(find|search) books by {author}"
6385 }
6386 ],
6387 "matches": [
6388 {
6389 "inputs": {
6390 "author": "Le Guin"
6391 },
6392 "notes": [],
6393 "rejected": false,
6394 "specificity": 17,
6395 "tree": "library",
6396 "trigger": "(find|search) books by {author}"
6397 }
6398 ],
6399 "now": "2026-10-25T12:00:00+00:00",
6400 "step": {
6401 "kind": "intent",
6402 "node": "intent",
6403 "observed": {
6404 "cleaned": "search novels by Le Guin",
6405 "inputs": {
6406 "author": "Le Guin"
6407 },
6408 "steps": [],
6409 "trigger": "(find|search) books by {author}"
6410 },
6411 "summary": "'search novels by Le Guin'; library via '(find|search) books by {author}' (17 literal chars)"
6412 },
6413 "steps": [],
6414 "text": "search novels by Le Guin"
6415 },
6416 "text": "search novels by Le Guin"
6417 },
6418 {
6419 "expect": {
6420 "cleaned": "look up paperback books by Le Guin",
6421 "considered": [
6422 {
6423 "inputs": {
6424 "author": "Le Guin"
6425 },
6426 "notes": [],
6427 "rejected": false,
6428 "specificity": 17,
6429 "tree": "library",
6430 "trigger": "(find|search) books by {author}"
6431 }
6432 ],
6433 "matches": [
6434 {
6435 "inputs": {
6436 "author": "Le Guin"
6437 },
6438 "notes": [],
6439 "rejected": false,
6440 "specificity": 17,
6441 "tree": "library",
6442 "trigger": "(find|search) books by {author}"
6443 }
6444 ],
6445 "now": "2026-10-25T12:00:00+00:00",
6446 "step": {
6447 "kind": "intent",
6448 "node": "intent",
6449 "observed": {
6450 "cleaned": "look up paperback books by Le Guin",
6451 "inputs": {
6452 "author": "Le Guin"
6453 },
6454 "steps": [],
6455 "trigger": "(find|search) books by {author}"
6456 },
6457 "summary": "'look up paperback books by Le Guin'; library via '(find|search) books by {author}' (17 literal chars)"
6458 },
6459 "steps": [],
6460 "text": "look up paperback books by Le Guin"
6461 },
6462 "text": "look up paperback books by Le Guin"
6463 },
6464 {
6465 "expect": {
6466 "cleaned": "finnd books by Le Guin",
6467 "considered": [],
6468 "matches": [],
6469 "now": "2026-10-25T12:00:00+00:00",
6470 "step": {
6471 "kind": "intent",
6472 "node": "intent",
6473 "observed": {
6474 "cleaned": "finnd books by Le Guin",
6475 "steps": []
6476 },
6477 "summary": "'finnd books by Le Guin'; no trigger matched"
6478 },
6479 "steps": [],
6480 "text": "finnd books by Le Guin"
6481 },
6482 "text": "finnd books by Le Guin"
6483 },
6484 {
6485 "expect": {
6486 "cleaned": "serch books by Le Guin",
6487 "considered": [
6488 {
6489 "inputs": {
6490 "author": "Le Guin"
6491 },
6492 "notes": [
6493 "typo: serch->search"
6494 ],
6495 "rejected": false,
6496 "specificity": 17,
6497 "tree": "library",
6498 "trigger": "(find|search) books by {author}"
6499 }
6500 ],
6501 "matches": [
6502 {
6503 "inputs": {
6504 "author": "Le Guin"
6505 },
6506 "notes": [
6507 "typo: serch->search"
6508 ],
6509 "rejected": false,
6510 "specificity": 17,
6511 "tree": "library",
6512 "trigger": "(find|search) books by {author}"
6513 }
6514 ],
6515 "now": "2026-10-25T12:00:00+00:00",
6516 "step": {
6517 "kind": "intent",
6518 "node": "intent",
6519 "observed": {
6520 "cleaned": "serch books by Le Guin",
6521 "inputs": {
6522 "author": "Le Guin"
6523 },
6524 "notes": [
6525 "typo: serch->search"
6526 ],
6527 "steps": [],
6528 "trigger": "(find|search) books by {author}"
6529 },
6530 "summary": "'serch books by Le Guin'; library via '(find|search) books by {author}' (17 literal chars); typo: serch->search"
6531 },
6532 "steps": [],
6533 "text": "serch books by Le Guin"
6534 },
6535 "text": "serch books by Le Guin"
6536 },
6537 {
6538 "expect": {
6539 "cleaned": "top stories on book club",
6540 "considered": [
6541 {
6542 "inputs": {},
6543 "notes": [],
6544 "rejected": false,
6545 "specificity": 18,
6546 "tree": "library",
6547 "trigger": "top story on book club"
6548 }
6549 ],
6550 "matches": [
6551 {
6552 "inputs": {},
6553 "notes": [],
6554 "rejected": false,
6555 "specificity": 18,
6556 "tree": "library",
6557 "trigger": "top story on book club"
6558 }
6559 ],
6560 "now": "2026-10-25T12:00:00+00:00",
6561 "step": {
6562 "kind": "intent",
6563 "node": "intent",
6564 "observed": {
6565 "cleaned": "top stories on book club",
6566 "inputs": {},
6567 "steps": [],
6568 "trigger": "top story on book club"
6569 },
6570 "summary": "'top stories on book club'; library via 'top story on book club' (18 literal chars)"
6571 },
6572 "steps": [],
6573 "text": "top stories on book club"
6574 },
6575 "text": "top stories on book club"
6576 },
6577 {
6578 "expect": {
6579 "cleaned": "shout hello",
6580 "considered": [
6581 {
6582 "inputs": {
6583 "x": "hello"
6584 },
6585 "notes": [],
6586 "rejected": false,
6587 "specificity": 5,
6588 "tree": "tie-a",
6589 "trigger": "shout {x:word}"
6590 },
6591 {
6592 "inputs": {
6593 "y": "hello"
6594 },
6595 "notes": [],
6596 "rejected": false,
6597 "specificity": 5,
6598 "tree": "tie-b",
6599 "trigger": "shout {y:word}"
6600 }
6601 ],
6602 "matches": [
6603 {
6604 "inputs": {
6605 "x": "hello"
6606 },
6607 "notes": [],
6608 "rejected": false,
6609 "specificity": 5,
6610 "tree": "tie-a",
6611 "trigger": "shout {x:word}"
6612 },
6613 {
6614 "inputs": {
6615 "y": "hello"
6616 },
6617 "notes": [],
6618 "rejected": false,
6619 "specificity": 5,
6620 "tree": "tie-b",
6621 "trigger": "shout {y:word}"
6622 }
6623 ],
6624 "now": "2026-10-25T12:00:00+00:00",
6625 "step": {
6626 "kind": "intent",
6627 "node": "intent",
6628 "observed": {
6629 "cleaned": "shout hello",
6630 "steps": []
6631 },
6632 "summary": "'shout hello'; tie between ['tie-a', 'tie-b']"
6633 },
6634 "steps": [],
6635 "text": "shout hello"
6636 },
6637 "text": "shout hello"
6638 },
6639 {
6640 "expect": {
6641 "cleaned": "the usual",
6642 "considered": [
6643 {
6644 "inputs": {
6645 "what": "coffee"
6646 },
6647 "notes": [],
6648 "rejected": false,
6649 "specificity": 8,
6650 "tree": "fixed",
6651 "trigger": "the usual"
6652 }
6653 ],
6654 "matches": [
6655 {
6656 "inputs": {
6657 "what": "coffee"
6658 },
6659 "notes": [],
6660 "rejected": false,
6661 "specificity": 8,
6662 "tree": "fixed",
6663 "trigger": "the usual"
6664 }
6665 ],
6666 "now": "2026-10-25T12:00:00+00:00",
6667 "step": {
6668 "kind": "intent",
6669 "node": "intent",
6670 "observed": {
6671 "cleaned": "the usual",
6672 "inputs": {
6673 "what": "coffee"
6674 },
6675 "steps": [],
6676 "trigger": "the usual"
6677 },
6678 "summary": "'the usual'; fixed via 'the usual' (8 literal chars)"
6679 },
6680 "steps": [],
6681 "text": "the usual"
6682 },
6683 "text": "the usual"
6684 },
6685 {
6686 "expect": {
6687 "cleaned": "order a latte",
6688 "considered": [
6689 {
6690 "inputs": {
6691 "drink": "latte"
6692 },
6693 "notes": [],
6694 "rejected": false,
6695 "specificity": 6,
6696 "tree": "order",
6697 "trigger": "order a {drink:word}"
6698 }
6699 ],
6700 "matches": [
6701 {
6702 "inputs": {
6703 "drink": "latte"
6704 },
6705 "notes": [],
6706 "rejected": false,
6707 "specificity": 6,
6708 "tree": "order",
6709 "trigger": "order a {drink:word}"
6710 }
6711 ],
6712 "now": "2026-10-25T12:00:00+00:00",
6713 "step": {
6714 "kind": "intent",
6715 "node": "intent",
6716 "observed": {
6717 "cleaned": "order a latte",
6718 "inputs": {
6719 "drink": "latte"
6720 },
6721 "steps": [],
6722 "trigger": "order a {drink:word}"
6723 },
6724 "summary": "'order a latte'; order via 'order a {drink:word}' (6 literal chars)"
6725 },
6726 "steps": [],
6727 "text": "order a latte"
6728 },
6729 "text": "order a latte"
6730 },
6731 {
6732 "expect": {
6733 "cleaned": "it is the usual",
6734 "considered": [],
6735 "matches": [],
6736 "now": "2026-10-25T12:00:00+00:00",
6737 "step": {
6738 "kind": "intent",
6739 "node": "intent",
6740 "observed": {
6741 "cleaned": "it is the usual",
6742 "steps": [
6743 "contractions: it's->it is"
6744 ]
6745 },
6746 "summary": "\"it's the usual\" -> 'it is the usual'; no trigger matched"
6747 },
6748 "steps": [
6749 "contractions: it's->it is"
6750 ],
6751 "text": "it's the usual"
6752 },
6753 "text": "it's the usual"
6754 }
6755 ],
6756 "runs": [
6757 {
6758 "expect": {
6759 "inputs": {
6760 "count": 1
6761 },
6762 "output": {
6763 "stories": [
6764 {
6765 "by": "alice",
6766 "id": 101,
6767 "score": 42,
6768 "title": "Pyrtan 0.1",
6769 "url": "https://example.org/p"
6770 }
6771 ]
6772 },
6773 "say": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p",
6774 "status": "done",
6775 "trace": {
6776 "gaps": [],
6777 "steps": [
6778 {
6779 "depth": 0,
6780 "kind": "intent",
6781 "next": "fetch-ids",
6782 "node": "intent",
6783 "observed": {
6784 "cleaned": "What is the top story on Hacker News",
6785 "inputs": {
6786 "count": 1
6787 },
6788 "steps": [
6789 "contractions: What's->what is"
6790 ],
6791 "trigger": "[what's the] top story on hacker news"
6792 },
6793 "summary": "\"What's the top story on Hacker News?\" -> 'What is the top story on Hacker News'; hacker-news via \"[what's the] top story on hacker news\" (29 literal chars)"
6794 },
6795 {
6796 "depth": 0,
6797 "kind": "observe",
6798 "next": "check",
6799 "node": "fetch-ids",
6800 "observed": {
6801 "ids": [
6802 101,
6803 102,
6804 103
6805 ]
6806 },
6807 "summary": "GET https://hacker-news.firebaseio.com/v0/topstories.json -> 200; ids=[101, 102, 103]"
6808 },
6809 {
6810 "chosen": "eq 200",
6811 "depth": 0,
6812 "kind": "decide",
6813 "next": "any",
6814 "node": "check",
6815 "observed": 200,
6816 "summary": "state.ids_resp.status = 200"
6817 },
6818 {
6819 "chosen": "type 'list'",
6820 "depth": 0,
6821 "kind": "decide",
6822 "next": "stories",
6823 "node": "any",
6824 "observed": [
6825 101,
6826 102,
6827 103
6828 ],
6829 "summary": "state.ids = [101, 102, 103]"
6830 },
6831 {
6832 "depth": 1,
6833 "kind": "observe",
6834 "next": "kind",
6835 "node": "fetch",
6836 "observed": {
6837 "by": "alice",
6838 "score": 42,
6839 "title": "Pyrtan 0.1",
6840 "type": "story",
6841 "url": "https://example.org/p"
6842 },
6843 "summary": "GET https://hacker-news.firebaseio.com/v0/item/101.json -> 200; title=Pyrtan 0.1, url=https://example.org/p, score=42, by=alice, type=story"
6844 },
6845 {
6846 "chosen": "eq 'story'",
6847 "depth": 1,
6848 "kind": "decide",
6849 "next": "has-url",
6850 "node": "kind",
6851 "observed": "story",
6852 "summary": "state.type = story"
6853 },
6854 {
6855 "chosen": "exists True",
6856 "depth": 1,
6857 "kind": "decide",
6858 "next": "reply",
6859 "node": "has-url",
6860 "observed": "https://example.org/p",
6861 "summary": "state.url = https://example.org/p"
6862 },
6863 {
6864 "depth": 1,
6865 "kind": "done",
6866 "next": null,
6867 "node": "reply",
6868 "summary": "Pyrtan 0.1 (42 points, by alice) https://example.org/p"
6869 },
6870 {
6871 "depth": 0,
6872 "kind": "each",
6873 "next": "reply",
6874 "node": "stories",
6875 "summary": "1 item(s)"
6876 },
6877 {
6878 "depth": 0,
6879 "kind": "done",
6880 "next": null,
6881 "node": "reply",
6882 "summary": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p"
6883 }
6884 ],
6885 "tree": "hacker-news"
6886 },
6887 "tree": "hacker-news"
6888 },
6889 "inputs": {
6890 "count": 1
6891 },
6892 "intent": "What's the top story on Hacker News?",
6893 "tree": "hacker-news"
6894 },
6895 {
6896 "expect": {
6897 "inputs": {
6898 "count": 3
6899 },
6900 "output": {
6901 "stories": [
6902 {
6903 "by": "alice",
6904 "id": 101,
6905 "score": 42,
6906 "title": "Pyrtan 0.1",
6907 "url": "https://example.org/p"
6908 },
6909 {
6910 "by": "x",
6911 "id": 102,
6912 "score": 7,
6913 "title": "Ask HN: self post",
6914 "url": "https://news.ycombinator.com/item?id=102"
6915 },
6916 null
6917 ]
6918 },
6919 "say": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p",
6920 "status": "done",
6921 "trace": {
6922 "gaps": [],
6923 "steps": [
6924 {
6925 "depth": 0,
6926 "kind": "intent",
6927 "next": "fetch-ids",
6928 "node": "intent",
6929 "observed": {
6930 "cleaned": "top 3 stories on hacker news",
6931 "inputs": {
6932 "count": 3
6933 },
6934 "steps": [],
6935 "trigger": "top {count:int} stories on hacker news"
6936 },
6937 "summary": "'top 3 stories on hacker news'; hacker-news via 'top {count:int} stories on hacker news' (22 literal chars)"
6938 },
6939 {
6940 "depth": 0,
6941 "kind": "observe",
6942 "next": "check",
6943 "node": "fetch-ids",
6944 "observed": {
6945 "ids": [
6946 101,
6947 102,
6948 103
6949 ]
6950 },
6951 "summary": "GET https://hacker-news.firebaseio.com/v0/topstories.json -> 200; ids=[101, 102, 103]"
6952 },
6953 {
6954 "chosen": "eq 200",
6955 "depth": 0,
6956 "kind": "decide",
6957 "next": "any",
6958 "node": "check",
6959 "observed": 200,
6960 "summary": "state.ids_resp.status = 200"
6961 },
6962 {
6963 "chosen": "type 'list'",
6964 "depth": 0,
6965 "kind": "decide",
6966 "next": "stories",
6967 "node": "any",
6968 "observed": [
6969 101,
6970 102,
6971 103
6972 ],
6973 "summary": "state.ids = [101, 102, 103]"
6974 },
6975 {
6976 "depth": 1,
6977 "kind": "observe",
6978 "next": "kind",
6979 "node": "fetch",
6980 "observed": {
6981 "by": "alice",
6982 "score": 42,
6983 "title": "Pyrtan 0.1",
6984 "type": "story",
6985 "url": "https://example.org/p"
6986 },
6987 "summary": "GET https://hacker-news.firebaseio.com/v0/item/101.json -> 200; title=Pyrtan 0.1, url=https://example.org/p, score=42, by=alice, type=story"
6988 },
6989 {
6990 "chosen": "eq 'story'",
6991 "depth": 1,
6992 "kind": "decide",
6993 "next": "has-url",
6994 "node": "kind",
6995 "observed": "story",
6996 "summary": "state.type = story"
6997 },
6998 {
6999 "chosen": "exists True",
7000 "depth": 1,
7001 "kind": "decide",
7002 "next": "reply",
7003 "node": "has-url",
7004 "observed": "https://example.org/p",
7005 "summary": "state.url = https://example.org/p"
7006 },
7007 {
7008 "depth": 1,
7009 "kind": "done",
7010 "next": null,
7011 "node": "reply",
7012 "summary": "Pyrtan 0.1 (42 points, by alice) https://example.org/p"
7013 },
7014 {
7015 "depth": 1,
7016 "kind": "observe",
7017 "next": "kind",
7018 "node": "fetch",
7019 "observed": {
7020 "by": "x",
7021 "score": 7,
7022 "title": "Ask HN: self post",
7023 "type": "story",
7024 "url": null
7025 },
7026 "summary": "GET https://hacker-news.firebaseio.com/v0/item/102.json -> 200; title=Ask HN: self post, url=None, score=7, by=x, type=story"
7027 },
7028 {
7029 "chosen": "eq 'story'",
7030 "depth": 1,
7031 "kind": "decide",
7032 "next": "has-url",
7033 "node": "kind",
7034 "observed": "story",
7035 "summary": "state.type = story"
7036 },
7037 {
7038 "chosen": "else",
7039 "depth": 1,
7040 "kind": "decide",
7041 "next": "self-post",
7042 "node": "has-url",
7043 "summary": "state.url = <missing>"
7044 },
7045 {
7046 "depth": 1,
7047 "kind": "set",
7048 "next": "reply",
7049 "node": "self-post",
7050 "observed": {
7051 "url": "https://news.ycombinator.com/item?id=102"
7052 },
7053 "summary": "url=https://news.ycombinator.com/item?id=102"
7054 },
7055 {
7056 "depth": 1,
7057 "kind": "done",
7058 "next": null,
7059 "node": "reply",
7060 "summary": "Ask HN: self post (7 points, by x) https://news.ycombinator.com/item?id=102"
7061 },
7062 {
7063 "depth": 1,
7064 "kind": "observe",
7065 "next": "kind",
7066 "node": "fetch",
7067 "observed": {
7068 "by": "y",
7069 "score": null,
7070 "title": null,
7071 "type": "comment",
7072 "url": null
7073 },
7074 "summary": "GET https://hacker-news.firebaseio.com/v0/item/103.json -> 200; title=None, url=None, score=None, by=y, type=comment"
7075 },
7076 {
7077 "chosen": "else",
7078 "depth": 1,
7079 "kind": "decide",
7080 "next": "not-a-story",
7081 "node": "kind",
7082 "observed": "comment",
7083 "summary": "state.type = comment"
7084 },
7085 {
7086 "depth": 1,
7087 "kind": "fail",
7088 "next": null,
7089 "node": "not-a-story",
7090 "summary": "not-a-story: Item 103 is a comment, not a story."
7091 },
7092 {
7093 "depth": 0,
7094 "kind": "each",
7095 "next": "reply",
7096 "node": "stories",
7097 "summary": "3 item(s), 1 failed"
7098 },
7099 {
7100 "depth": 0,
7101 "kind": "done",
7102 "next": null,
7103 "node": "reply",
7104 "summary": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p"
7105 }
7106 ],
7107 "tree": "hacker-news"
7108 },
7109 "tree": "hacker-news"
7110 },
7111 "inputs": {
7112 "count": 3
7113 },
7114 "intent": "top 3 stories on hacker news",
7115 "tree": "hacker-news"
7116 },
7117 {
7118 "expect": {
7119 "inputs": {},
7120 "reason": "bad-input",
7121 "say": "input 'count' must be at most 30, got 99",
7122 "status": "error",
7123 "trace": {
7124 "gaps": [],
7125 "steps": [
7126 {
7127 "depth": 0,
7128 "kind": "intent",
7129 "next": "fetch-ids",
7130 "node": "intent",
7131 "observed": {
7132 "cleaned": "top 99 stories on hacker news",
7133 "inputs": {
7134 "count": 99
7135 },
7136 "steps": [],
7137 "trigger": "top {count:int} stories on hacker news"
7138 },
7139 "summary": "'top 99 stories on hacker news'; hacker-news via 'top {count:int} stories on hacker news' (22 literal chars)"
7140 }
7141 ],
7142 "tree": "hacker-news"
7143 },
7144 "tree": "hacker-news"
7145 },
7146 "inputs": {
7147 "count": 99
7148 },
7149 "intent": "top 99 stories on hacker news",
7150 "tree": "hacker-news"
7151 },
7152 {
7153 "expect": {
7154 "inputs": {
7155 "id": 999
7156 },
7157 "reason": "no-such-item",
7158 "say": "There is no Hacker News item 999.",
7159 "status": "failed",
7160 "trace": {
7161 "gaps": [],
7162 "steps": [
7163 {
7164 "depth": 0,
7165 "kind": "intent",
7166 "next": "fetch",
7167 "node": "intent",
7168 "observed": {
7169 "cleaned": "hacker news item 999",
7170 "inputs": {
7171 "id": 999
7172 },
7173 "steps": [],
7174 "trigger": "hacker news item {id:int}"
7175 },
7176 "summary": "'hacker news item 999'; hacker-news-item via 'hacker news item {id:int}' (14 literal chars)"
7177 },
7178 {
7179 "depth": 0,
7180 "kind": "observe",
7181 "next": "kind",
7182 "node": "fetch",
7183 "observed": {
7184 "by": null,
7185 "score": null,
7186 "title": null,
7187 "type": null,
7188 "url": null
7189 },
7190 "summary": "GET https://hacker-news.firebaseio.com/v0/item/999.json -> 200; title=None, url=None, score=None, by=None, type=None"
7191 },
7192 {
7193 "chosen": "exists False",
7194 "depth": 0,
7195 "kind": "decide",
7196 "next": "missing",
7197 "node": "kind",
7198 "summary": "state.type = <missing>"
7199 },
7200 {
7201 "depth": 0,
7202 "kind": "fail",
7203 "next": null,
7204 "node": "missing",
7205 "summary": "no-such-item: There is no Hacker News item 999."
7206 }
7207 ],
7208 "tree": "hacker-news-item"
7209 },
7210 "tree": "hacker-news-item"
7211 },
7212 "inputs": {
7213 "id": 999
7214 },
7215 "intent": "hacker news item 999",
7216 "tree": "hacker-news-item"
7217 },
7218 {
7219 "expect": {
7220 "inputs": {
7221 "place": "Oslo",
7222 "units": "celsius"
7223 },
7224 "output": {
7225 "country": "Norway",
7226 "humidity": 80,
7227 "place": "Oslo",
7228 "sky": "raining",
7229 "temperature": 4.2,
7230 "unit": "°C",
7231 "wind_kmh": 12.3
7232 },
7233 "say": "Oslo, Norway: 4.2°C, raining, humidity 80%, wind 12.3 km/h.",
7234 "status": "done",
7235 "trace": {
7236 "gaps": [],
7237 "steps": [
7238 {
7239 "depth": 0,
7240 "kind": "intent",
7241 "next": "geocode",
7242 "node": "intent",
7243 "observed": {
7244 "cleaned": "weather in Oslo",
7245 "inputs": {
7246 "place": "Oslo"
7247 },
7248 "steps": [],
7249 "trigger": "[what's the] weather in {place}"
7250 },
7251 "summary": "'weather in Oslo'; weather via \"[what's the] weather in {place}\" (18 literal chars)"
7252 },
7253 {
7254 "depth": 0,
7255 "kind": "observe",
7256 "next": "found",
7257 "node": "geocode",
7258 "observed": {
7259 "country": "Norway",
7260 "hit": {
7261 "country": "Norway",
7262 "latitude": 59.91,
7263 "longitude": 10.75,
7264 "name": "Oslo"
7265 },
7266 "lat": 59.91,
7267 "lon": 10.75,
7268 "name": "Oslo"
7269 },
7270 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Oslo&count=1&language=en&format=json -> 200; hit={'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}, lat=59.91, lon=10.75, name=Oslo, country=Norway"
7271 },
7272 {
7273 "chosen": "exists True",
7274 "depth": 0,
7275 "kind": "decide",
7276 "next": "forecast",
7277 "node": "found",
7278 "observed": {
7279 "country": "Norway",
7280 "latitude": 59.91,
7281 "longitude": 10.75,
7282 "name": "Oslo"
7283 },
7284 "summary": "state.hit = {'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}"
7285 },
7286 {
7287 "depth": 0,
7288 "kind": "observe",
7289 "next": "have-temp",
7290 "node": "forecast",
7291 "observed": {
7292 "code": 61,
7293 "humidity": 80,
7294 "temp": 4.2,
7295 "unit": "°C",
7296 "wind": 12.3
7297 },
7298 "summary": "GET https://api.open-meteo.com/v1/forecast?latitude=59.91&longitude=10.75&current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code&temperature_unit=celsius -> 200; temp=4.2, humidity=80, wind=12.3, code=61, unit=°C"
7299 },
7300 {
7301 "chosen": "type 'number'",
7302 "depth": 0,
7303 "kind": "decide",
7304 "next": "sky",
7305 "node": "have-temp",
7306 "observed": 4.2,
7307 "summary": "state.temp = 4.2"
7308 },
7309 {
7310 "chosen": "between [51, 67]",
7311 "depth": 0,
7312 "kind": "decide",
7313 "next": "rain",
7314 "node": "sky",
7315 "observed": 61,
7316 "summary": "state.code = 61"
7317 },
7318 {
7319 "depth": 0,
7320 "kind": "set",
7321 "next": "reply",
7322 "node": "rain",
7323 "observed": {
7324 "sky": "raining"
7325 },
7326 "summary": "sky=raining"
7327 },
7328 {
7329 "depth": 0,
7330 "kind": "done",
7331 "next": null,
7332 "node": "reply",
7333 "summary": "Oslo, Norway: 4.2°C, raining, humidity 80%, wind 12.3 km/h."
7334 }
7335 ],
7336 "tree": "weather"
7337 },
7338 "tree": "weather"
7339 },
7340 "inputs": {
7341 "place": "Oslo"
7342 },
7343 "intent": "weather in Oslo",
7344 "tree": "weather"
7345 },
7346 {
7347 "expect": {
7348 "inputs": {
7349 "place": "Oslo",
7350 "units": "fahrenheit"
7351 },
7352 "output": {
7353 "country": "Norway",
7354 "humidity": 80,
7355 "place": "Oslo",
7356 "sky": "clear",
7357 "temperature": 39.6,
7358 "unit": "°F",
7359 "wind_kmh": 12.3
7360 },
7361 "say": "Oslo, Norway: 39.6°F, clear, humidity 80%, wind 12.3 km/h.",
7362 "status": "done",
7363 "trace": {
7364 "gaps": [],
7365 "steps": [
7366 {
7367 "depth": 0,
7368 "kind": "observe",
7369 "next": "found",
7370 "node": "geocode",
7371 "observed": {
7372 "country": "Norway",
7373 "hit": {
7374 "country": "Norway",
7375 "latitude": 59.91,
7376 "longitude": 10.75,
7377 "name": "Oslo"
7378 },
7379 "lat": 59.91,
7380 "lon": 10.75,
7381 "name": "Oslo"
7382 },
7383 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Oslo&count=1&language=en&format=json -> 200; hit={'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}, lat=59.91, lon=10.75, name=Oslo, country=Norway"
7384 },
7385 {
7386 "chosen": "exists True",
7387 "depth": 0,
7388 "kind": "decide",
7389 "next": "forecast",
7390 "node": "found",
7391 "observed": {
7392 "country": "Norway",
7393 "latitude": 59.91,
7394 "longitude": 10.75,
7395 "name": "Oslo"
7396 },
7397 "summary": "state.hit = {'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}"
7398 },
7399 {
7400 "depth": 0,
7401 "kind": "observe",
7402 "next": "have-temp",
7403 "node": "forecast",
7404 "observed": {
7405 "code": 0,
7406 "humidity": 80,
7407 "temp": 39.6,
7408 "unit": "°F",
7409 "wind": 12.3
7410 },
7411 "summary": "GET https://api.open-meteo.com/v1/forecast?latitude=59.91&longitude=10.75&current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code&temperature_unit=fahrenheit -> 200; temp=39.6, humidity=80, wind=12.3, code=0, unit=°F"
7412 },
7413 {
7414 "chosen": "type 'number'",
7415 "depth": 0,
7416 "kind": "decide",
7417 "next": "sky",
7418 "node": "have-temp",
7419 "observed": 39.6,
7420 "summary": "state.temp = 39.6"
7421 },
7422 {
7423 "chosen": "eq 0",
7424 "depth": 0,
7425 "kind": "decide",
7426 "next": "clear",
7427 "node": "sky",
7428 "observed": 0,
7429 "summary": "state.code = 0"
7430 },
7431 {
7432 "depth": 0,
7433 "kind": "set",
7434 "next": "reply",
7435 "node": "clear",
7436 "observed": {
7437 "sky": "clear"
7438 },
7439 "summary": "sky=clear"
7440 },
7441 {
7442 "depth": 0,
7443 "kind": "done",
7444 "next": null,
7445 "node": "reply",
7446 "summary": "Oslo, Norway: 39.6°F, clear, humidity 80%, wind 12.3 km/h."
7447 }
7448 ],
7449 "tree": "weather"
7450 },
7451 "tree": "weather"
7452 },
7453 "inputs": {
7454 "place": "Oslo",
7455 "units": "fahrenheit"
7456 },
7457 "tree": "weather"
7458 },
7459 {
7460 "expect": {
7461 "inputs": {},
7462 "reason": "bad-input",
7463 "say": "input 'units' must be one of ['celsius', 'fahrenheit'], got 'kelvin'",
7464 "status": "error",
7465 "trace": {
7466 "gaps": [],
7467 "steps": [],
7468 "tree": "weather"
7469 },
7470 "tree": "weather"
7471 },
7472 "inputs": {
7473 "place": "Oslo",
7474 "units": "kelvin"
7475 },
7476 "tree": "weather"
7477 },
7478 {
7479 "expect": {
7480 "inputs": {
7481 "place": "Nowhereville",
7482 "units": "celsius"
7483 },
7484 "reason": "unknown-place",
7485 "say": "I could not find a place called Nowhereville.",
7486 "status": "failed",
7487 "trace": {
7488 "gaps": [],
7489 "steps": [
7490 {
7491 "depth": 0,
7492 "kind": "intent",
7493 "next": "geocode",
7494 "node": "intent",
7495 "observed": {
7496 "cleaned": "how cold is it in Nowhereville",
7497 "inputs": {
7498 "place": "Nowhereville"
7499 },
7500 "steps": [],
7501 "trigger": "how [hot|cold|warm] is it in {place}"
7502 },
7503 "summary": "'how cold is it in Nowhereville'; weather via 'how [hot|cold|warm] is it in {place}' (20 literal chars)"
7504 },
7505 {
7506 "depth": 0,
7507 "kind": "observe",
7508 "next": "found",
7509 "node": "geocode",
7510 "observed": {
7511 "country": null,
7512 "hit": null,
7513 "lat": null,
7514 "lon": null,
7515 "name": null
7516 },
7517 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Nowhereville&count=1&language=en&format=json -> 200; hit=None, lat=None, lon=None, name=None, country=None"
7518 },
7519 {
7520 "chosen": "else",
7521 "depth": 0,
7522 "kind": "decide",
7523 "next": "unknown-place",
7524 "node": "found",
7525 "summary": "state.hit = <missing>"
7526 },
7527 {
7528 "depth": 0,
7529 "kind": "fail",
7530 "next": null,
7531 "node": "unknown-place",
7532 "summary": "unknown-place: I could not find a place called Nowhereville."
7533 }
7534 ],
7535 "tree": "weather"
7536 },
7537 "tree": "weather"
7538 },
7539 "inputs": {
7540 "place": "Nowhereville"
7541 },
7542 "intent": "how cold is it in Nowhereville",
7543 "tree": "weather"
7544 },
7545 {
7546 "expect": {
7547 "inputs": {
7548 "host": "example.com"
7549 },
7550 "output": {
7551 "elapsed_ms": 0,
7552 "host": "example.com",
7553 "status": 200,
7554 "up": true
7555 },
7556 "say": "example.com is up: 200 in 0 ms.",
7557 "status": "done",
7558 "trace": {
7559 "gaps": [],
7560 "steps": [
7561 {
7562 "depth": 0,
7563 "kind": "intent",
7564 "next": "probe",
7565 "node": "intent",
7566 "observed": {
7567 "cleaned": "is Example.com up",
7568 "inputs": {
7569 "host": "example.com"
7570 },
7571 "notes": [
7572 "host: 'Example.com' read as 'example.com'"
7573 ],
7574 "steps": [],
7575 "trigger": "is {host:host} up"
7576 },
7577 "summary": "'is Example.com up'; site-status via 'is {host:host} up' (4 literal chars); host: 'Example.com' read as 'example.com'"
7578 },
7579 {
7580 "depth": 0,
7581 "kind": "observe",
7582 "next": "status",
7583 "node": "probe",
7584 "summary": "GET https://example.com/ -> 200"
7585 },
7586 {
7587 "chosen": "between [200, 399]",
7588 "depth": 0,
7589 "kind": "decide",
7590 "next": "up",
7591 "node": "status",
7592 "observed": 200,
7593 "summary": "state.resp.status = 200"
7594 },
7595 {
7596 "depth": 0,
7597 "kind": "done",
7598 "next": null,
7599 "node": "up",
7600 "summary": "example.com is up: 200 in 0 ms."
7601 }
7602 ],
7603 "tree": "site-status"
7604 },
7605 "tree": "site-status"
7606 },
7607 "inputs": {
7608 "host": "example.com"
7609 },
7610 "intent": "is Example.com up",
7611 "tree": "site-status"
7612 },
7613 {
7614 "expect": {
7615 "inputs": {
7616 "host": "broken.example"
7617 },
7618 "output": {
7619 "host": "broken.example",
7620 "status": 503,
7621 "up": false
7622 },
7623 "reason": "server-error",
7624 "say": "broken.example is answering 503: up, but broken.",
7625 "status": "failed",
7626 "trace": {
7627 "gaps": [],
7628 "steps": [
7629 {
7630 "depth": 0,
7631 "kind": "intent",
7632 "next": "probe",
7633 "node": "intent",
7634 "observed": {
7635 "cleaned": "is broken.example up",
7636 "inputs": {
7637 "host": "broken.example"
7638 },
7639 "steps": [],
7640 "trigger": "is {host:host} up"
7641 },
7642 "summary": "'is broken.example up'; site-status via 'is {host:host} up' (4 literal chars)"
7643 },
7644 {
7645 "depth": 0,
7646 "kind": "observe",
7647 "next": "status",
7648 "node": "probe",
7649 "summary": "GET https://broken.example/ -> 503"
7650 },
7651 {
7652 "chosen": "gte 500",
7653 "depth": 0,
7654 "kind": "decide",
7655 "next": "erroring",
7656 "node": "status",
7657 "observed": 503,
7658 "summary": "state.resp.status = 503"
7659 },
7660 {
7661 "depth": 0,
7662 "kind": "fail",
7663 "next": null,
7664 "node": "erroring",
7665 "summary": "server-error: broken.example is answering 503: up, but broken."
7666 }
7667 ],
7668 "tree": "site-status"
7669 },
7670 "tree": "site-status"
7671 },
7672 "inputs": {
7673 "host": "broken.example"
7674 },
7675 "intent": "is broken.example up",
7676 "tree": "site-status"
7677 },
7678 {
7679 "expect": {
7680 "inputs": {
7681 "host": "teapot.example"
7682 },
7683 "output": {
7684 "host": "teapot.example",
7685 "note": "client error on /",
7686 "status": 418,
7687 "up": true
7688 },
7689 "say": "teapot.example answers but refuses the front page (418); the server is up.",
7690 "status": "done",
7691 "trace": {
7692 "gaps": [],
7693 "steps": [
7694 {
7695 "depth": 0,
7696 "kind": "intent",
7697 "next": "probe",
7698 "node": "intent",
7699 "observed": {
7700 "cleaned": "check status of teapot.example",
7701 "inputs": {
7702 "host": "teapot.example"
7703 },
7704 "steps": [],
7705 "trigger": "[check] status of {host:host}"
7706 },
7707 "summary": "'check status of teapot.example'; site-status via '[check] status of {host:host}' (13 literal chars)"
7708 },
7709 {
7710 "depth": 0,
7711 "kind": "observe",
7712 "next": "status",
7713 "node": "probe",
7714 "summary": "GET https://teapot.example/ -> 418"
7715 },
7716 {
7717 "chosen": "between [400, 499]",
7718 "depth": 0,
7719 "kind": "decide",
7720 "next": "refusing",
7721 "node": "status",
7722 "observed": 418,
7723 "summary": "state.resp.status = 418"
7724 },
7725 {
7726 "depth": 0,
7727 "kind": "done",
7728 "next": null,
7729 "node": "refusing",
7730 "summary": "teapot.example answers but refuses the front page (418); the server is up."
7731 }
7732 ],
7733 "tree": "site-status"
7734 },
7735 "tree": "site-status"
7736 },
7737 "inputs": {
7738 "host": "teapot.example"
7739 },
7740 "intent": "check status of teapot.example",
7741 "tree": "site-status"
7742 },
7743 {
7744 "expect": {
7745 "inputs": {},
7746 "reason": "bad-input",
7747 "say": "input 'host' must match '(?i)^[a-z0-9.\\\\-]+(:[0-9]+)?$', got 'http://x'",
7748 "status": "error",
7749 "trace": {
7750 "gaps": [],
7751 "steps": [],
7752 "tree": "site-status"
7753 },
7754 "tree": "site-status"
7755 },
7756 "inputs": {
7757 "host": "http://x"
7758 },
7759 "tree": "site-status"
7760 },
7761 {
7762 "expect": {
7763 "inputs": {
7764 "repo": "b-i-t-8/pyrtan"
7765 },
7766 "output": {
7767 "name": "First",
7768 "prerelease": false,
7769 "published": "2026-10-24T00:00:00Z",
7770 "repo": "b-i-t-8/pyrtan",
7771 "tag": "v0.1.0",
7772 "url": "https://github.com/b-i-t-8/pyrtan/releases/tag/v0.1.0"
7773 },
7774 "say": "b-i-t-8/pyrtan: v0.1.0 (2026-10-24T00:00:00Z) https://github.com/b-i-t-8/pyrtan/releases/tag/v0.1.0",
7775 "status": "done",
7776 "trace": {
7777 "gaps": [],
7778 "steps": [
7779 {
7780 "depth": 0,
7781 "kind": "intent",
7782 "next": "fetch",
7783 "node": "intent",
7784 "observed": {
7785 "cleaned": "latest release of b-i-t-8/pyrtan",
7786 "inputs": {
7787 "repo": "b-i-t-8/pyrtan"
7788 },
7789 "steps": [],
7790 "trigger": "[what's the] latest release of {repo:word}"
7791 },
7792 "summary": "'latest release of b-i-t-8/pyrtan'; github-release via \"[what's the] latest release of {repo:word}\" (24 literal chars)"
7793 },
7794 {
7795 "depth": 0,
7796 "kind": "observe",
7797 "next": "status",
7798 "node": "fetch",
7799 "observed": {
7800 "name": "First",
7801 "prerelease": false,
7802 "published": "2026-10-24T00:00:00Z",
7803 "tag": "v0.1.0",
7804 "url": "https://github.com/b-i-t-8/pyrtan/releases/tag/v0.1.0"
7805 },
7806 "summary": "GET https://api.github.com/repos/b-i-t-8/pyrtan/releases/latest -> 200; tag=v0.1.0, name=First, published=2026-10-24T00:00:00Z, url=https://github.com/b-i-t-8/pyrtan/releases/tag/v0.1.0, prerelease=False"
7807 },
7808 {
7809 "chosen": "eq 200",
7810 "depth": 0,
7811 "kind": "decide",
7812 "next": "reply",
7813 "node": "status",
7814 "observed": 200,
7815 "summary": "state.resp.status = 200"
7816 },
7817 {
7818 "depth": 0,
7819 "kind": "done",
7820 "next": null,
7821 "node": "reply",
7822 "summary": "b-i-t-8/pyrtan: v0.1.0 (2026-10-24T00:00:00Z) https://github.com/b-i-t-8/pyrtan/releases/tag/v0.1.0"
7823 }
7824 ],
7825 "tree": "github-release"
7826 },
7827 "tree": "github-release"
7828 },
7829 "inputs": {
7830 "repo": "b-i-t-8/pyrtan"
7831 },
7832 "intent": "latest release of b-i-t-8/pyrtan",
7833 "tree": "github-release"
7834 },
7835 {
7836 "expect": {
7837 "inputs": {
7838 "repo": "b-i-t-8/nothing"
7839 },
7840 "reason": "no-release",
7841 "say": "b-i-t-8/nothing has no releases, or does not exist.",
7842 "status": "failed",
7843 "trace": {
7844 "gaps": [],
7845 "steps": [
7846 {
7847 "depth": 0,
7848 "kind": "intent",
7849 "next": "fetch",
7850 "node": "intent",
7851 "observed": {
7852 "cleaned": "latest b-i-t-8/nothing release",
7853 "inputs": {
7854 "repo": "b-i-t-8/nothing"
7855 },
7856 "steps": [],
7857 "trigger": "latest {repo:word} release"
7858 },
7859 "summary": "'latest b-i-t-8/nothing release'; github-release via 'latest {repo:word} release' (13 literal chars)"
7860 },
7861 {
7862 "depth": 0,
7863 "kind": "observe",
7864 "next": "status",
7865 "node": "fetch",
7866 "observed": {
7867 "name": null,
7868 "prerelease": null,
7869 "published": null,
7870 "tag": null,
7871 "url": null
7872 },
7873 "summary": "GET https://api.github.com/repos/b-i-t-8/nothing/releases/latest -> 404; tag=None, name=None, published=None, url=None, prerelease=None"
7874 },
7875 {
7876 "chosen": "eq 404",
7877 "depth": 0,
7878 "kind": "decide",
7879 "next": "none",
7880 "node": "status",
7881 "observed": 404,
7882 "summary": "state.resp.status = 404"
7883 },
7884 {
7885 "depth": 0,
7886 "kind": "fail",
7887 "next": null,
7888 "node": "none",
7889 "summary": "no-release: b-i-t-8/nothing has no releases, or does not exist."
7890 }
7891 ],
7892 "tree": "github-release"
7893 },
7894 "tree": "github-release"
7895 },
7896 "inputs": {
7897 "repo": "b-i-t-8/nothing"
7898 },
7899 "intent": "latest b-i-t-8/nothing release",
7900 "tree": "github-release"
7901 },
7902 {
7903 "expect": {
7904 "inputs": {
7905 "repo": "b-i-t-8/busy"
7906 },
7907 "reason": "rate-limited",
7908 "say": "GitHub is rate-limiting unauthenticated requests (403).",
7909 "status": "failed",
7910 "trace": {
7911 "gaps": [],
7912 "steps": [
7913 {
7914 "depth": 0,
7915 "kind": "intent",
7916 "next": "fetch",
7917 "node": "intent",
7918 "observed": {
7919 "cleaned": "latest release of b-i-t-8/busy",
7920 "inputs": {
7921 "repo": "b-i-t-8/busy"
7922 },
7923 "steps": [],
7924 "trigger": "[what's the] latest release of {repo:word}"
7925 },
7926 "summary": "'latest release of b-i-t-8/busy'; github-release via \"[what's the] latest release of {repo:word}\" (24 literal chars)"
7927 },
7928 {
7929 "depth": 0,
7930 "kind": "observe",
7931 "next": "status",
7932 "node": "fetch",
7933 "observed": {
7934 "name": null,
7935 "prerelease": null,
7936 "published": null,
7937 "tag": null,
7938 "url": null
7939 },
7940 "summary": "GET https://api.github.com/repos/b-i-t-8/busy/releases/latest -> 403; tag=None, name=None, published=None, url=None, prerelease=None"
7941 },
7942 {
7943 "chosen": "in [403, 429]",
7944 "depth": 0,
7945 "kind": "decide",
7946 "next": "rate-limited",
7947 "node": "status",
7948 "observed": 403,
7949 "summary": "state.resp.status = 403"
7950 },
7951 {
7952 "depth": 0,
7953 "kind": "fail",
7954 "next": null,
7955 "node": "rate-limited",
7956 "summary": "rate-limited: GitHub is rate-limiting unauthenticated requests (403)."
7957 }
7958 ],
7959 "tree": "github-release"
7960 },
7961 "tree": "github-release"
7962 },
7963 "inputs": {
7964 "repo": "b-i-t-8/busy"
7965 },
7966 "intent": "latest release of b-i-t-8/busy",
7967 "tree": "github-release"
7968 },
7969 {
7970 "expect": {
7971 "inputs": {
7972 "place": "Paris",
7973 "units": "celsius"
7974 },
7975 "reason": "transport",
7976 "say": "geocode: no recorded response for GET https://geocoding-api.open-meteo.com/v1/search?name=Paris&count=1&language=en&format=json",
7977 "status": "error",
7978 "trace": {
7979 "gaps": [],
7980 "steps": [
7981 {
7982 "depth": 0,
7983 "kind": "intent",
7984 "next": "geocode",
7985 "node": "intent",
7986 "observed": {
7987 "cleaned": "weather in Paris",
7988 "inputs": {
7989 "place": "Paris"
7990 },
7991 "steps": [],
7992 "trigger": "[what's the] weather in {place}"
7993 },
7994 "summary": "'weather in Paris'; weather via \"[what's the] weather in {place}\" (18 literal chars)"
7995 },
7996 {
7997 "depth": 0,
7998 "kind": "observe",
7999 "next": null,
8000 "node": "geocode",
8001 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Paris&count=1&language=en&format=json failed: no recorded response for GET https://geocoding-api.open-meteo.com/v1/search?name=Paris&count=1&language=en&format=json"
8002 }
8003 ],
8004 "tree": "weather"
8005 },
8006 "tree": "weather"
8007 },
8008 "inputs": {
8009 "place": "Paris"
8010 },
8011 "intent": "weather in Paris",
8012 "tree": "weather"
8013 },
8014 {
8015 "expect": {
8016 "inputs": {
8017 "id": 101
8018 },
8019 "output": {
8020 "by": "alice",
8021 "id": 101,
8022 "score": 42,
8023 "title": "Pyrtan 0.1",
8024 "url": "https://example.org/p"
8025 },
8026 "say": "Pyrtan 0.1 (42 points, by alice) https://example.org/p",
8027 "status": "done",
8028 "trace": {
8029 "gaps": [],
8030 "steps": [
8031 {
8032 "depth": 0,
8033 "kind": "observe",
8034 "next": "kind",
8035 "node": "fetch",
8036 "observed": {
8037 "by": "alice",
8038 "score": 42,
8039 "title": "Pyrtan 0.1",
8040 "type": "story",
8041 "url": "https://example.org/p"
8042 },
8043 "summary": "GET https://hacker-news.firebaseio.com/v0/item/101.json -> 200; title=Pyrtan 0.1, url=https://example.org/p, score=42, by=alice, type=story"
8044 },
8045 {
8046 "chosen": "eq 'story'",
8047 "depth": 0,
8048 "kind": "decide",
8049 "next": "has-url",
8050 "node": "kind",
8051 "observed": "story",
8052 "summary": "state.type = story"
8053 },
8054 {
8055 "chosen": "exists True",
8056 "depth": 0,
8057 "kind": "decide",
8058 "next": "reply",
8059 "node": "has-url",
8060 "observed": "https://example.org/p",
8061 "summary": "state.url = https://example.org/p"
8062 },
8063 {
8064 "depth": 0,
8065 "kind": "done",
8066 "next": null,
8067 "node": "reply",
8068 "summary": "Pyrtan 0.1 (42 points, by alice) https://example.org/p"
8069 }
8070 ],
8071 "tree": "hacker-news-item"
8072 },
8073 "tree": "hacker-news-item"
8074 },
8075 "inputs": {
8076 "id": 101
8077 },
8078 "tree": "hacker-news-item"
8079 },
8080 {
8081 "expect": {
8082 "inputs": {
8083 "count": 2
8084 },
8085 "output": {
8086 "stories": [
8087 {
8088 "by": "alice",
8089 "id": 101,
8090 "score": 42,
8091 "title": "Pyrtan 0.1",
8092 "url": "https://example.org/p"
8093 },
8094 {
8095 "by": "x",
8096 "id": 102,
8097 "score": 7,
8098 "title": "Ask HN: self post",
8099 "url": "https://news.ycombinator.com/item?id=102"
8100 }
8101 ]
8102 },
8103 "say": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p",
8104 "status": "done",
8105 "trace": {
8106 "gaps": [],
8107 "steps": [
8108 {
8109 "depth": 0,
8110 "kind": "observe",
8111 "next": "check",
8112 "node": "fetch-ids",
8113 "observed": {
8114 "ids": [
8115 101,
8116 102,
8117 103
8118 ]
8119 },
8120 "summary": "GET https://hacker-news.firebaseio.com/v0/topstories.json -> 200; ids=[101, 102, 103]"
8121 },
8122 {
8123 "chosen": "eq 200",
8124 "depth": 0,
8125 "kind": "decide",
8126 "next": "any",
8127 "node": "check",
8128 "observed": 200,
8129 "summary": "state.ids_resp.status = 200"
8130 },
8131 {
8132 "chosen": "type 'list'",
8133 "depth": 0,
8134 "kind": "decide",
8135 "next": "stories",
8136 "node": "any",
8137 "observed": [
8138 101,
8139 102,
8140 103
8141 ],
8142 "summary": "state.ids = [101, 102, 103]"
8143 },
8144 {
8145 "depth": 1,
8146 "kind": "observe",
8147 "next": "kind",
8148 "node": "fetch",
8149 "observed": {
8150 "by": "alice",
8151 "score": 42,
8152 "title": "Pyrtan 0.1",
8153 "type": "story",
8154 "url": "https://example.org/p"
8155 },
8156 "summary": "GET https://hacker-news.firebaseio.com/v0/item/101.json -> 200; title=Pyrtan 0.1, url=https://example.org/p, score=42, by=alice, type=story"
8157 },
8158 {
8159 "chosen": "eq 'story'",
8160 "depth": 1,
8161 "kind": "decide",
8162 "next": "has-url",
8163 "node": "kind",
8164 "observed": "story",
8165 "summary": "state.type = story"
8166 },
8167 {
8168 "chosen": "exists True",
8169 "depth": 1,
8170 "kind": "decide",
8171 "next": "reply",
8172 "node": "has-url",
8173 "observed": "https://example.org/p",
8174 "summary": "state.url = https://example.org/p"
8175 },
8176 {
8177 "depth": 1,
8178 "kind": "done",
8179 "next": null,
8180 "node": "reply",
8181 "summary": "Pyrtan 0.1 (42 points, by alice) https://example.org/p"
8182 },
8183 {
8184 "depth": 1,
8185 "kind": "observe",
8186 "next": "kind",
8187 "node": "fetch",
8188 "observed": {
8189 "by": "x",
8190 "score": 7,
8191 "title": "Ask HN: self post",
8192 "type": "story",
8193 "url": null
8194 },
8195 "summary": "GET https://hacker-news.firebaseio.com/v0/item/102.json -> 200; title=Ask HN: self post, url=None, score=7, by=x, type=story"
8196 },
8197 {
8198 "chosen": "eq 'story'",
8199 "depth": 1,
8200 "kind": "decide",
8201 "next": "has-url",
8202 "node": "kind",
8203 "observed": "story",
8204 "summary": "state.type = story"
8205 },
8206 {
8207 "chosen": "else",
8208 "depth": 1,
8209 "kind": "decide",
8210 "next": "self-post",
8211 "node": "has-url",
8212 "summary": "state.url = <missing>"
8213 },
8214 {
8215 "depth": 1,
8216 "kind": "set",
8217 "next": "reply",
8218 "node": "self-post",
8219 "observed": {
8220 "url": "https://news.ycombinator.com/item?id=102"
8221 },
8222 "summary": "url=https://news.ycombinator.com/item?id=102"
8223 },
8224 {
8225 "depth": 1,
8226 "kind": "done",
8227 "next": null,
8228 "node": "reply",
8229 "summary": "Ask HN: self post (7 points, by x) https://news.ycombinator.com/item?id=102"
8230 },
8231 {
8232 "depth": 0,
8233 "kind": "each",
8234 "next": "reply",
8235 "node": "stories",
8236 "summary": "2 item(s)"
8237 },
8238 {
8239 "depth": 0,
8240 "kind": "done",
8241 "next": null,
8242 "node": "reply",
8243 "summary": "Top of Hacker News: Pyrtan 0.1 — https://example.org/p"
8244 }
8245 ],
8246 "tree": "hacker-news"
8247 },
8248 "tree": "hacker-news"
8249 },
8250 "inputs": {
8251 "count": 2
8252 },
8253 "tree": "hacker-news"
8254 },
8255 {
8256 "expect": {
8257 "inputs": {
8258 "mount": "/"
8259 },
8260 "output": {
8261 "mounted": "/",
8262 "percent": 71
8263 },
8264 "say": "/ is 71% full.",
8265 "status": "done",
8266 "trace": {
8267 "gaps": [],
8268 "steps": [
8269 {
8270 "depth": 0,
8271 "kind": "intent",
8272 "next": "df",
8273 "node": "intent",
8274 "observed": {
8275 "cleaned": "how full is the disk",
8276 "inputs": {},
8277 "steps": [],
8278 "trigger": "[how] full is the disk"
8279 },
8280 "summary": "'how full is the disk'; disk-space via '[how] full is the disk' (16 literal chars)"
8281 },
8282 {
8283 "depth": 0,
8284 "kind": "observe",
8285 "next": "ran",
8286 "node": "df",
8287 "observed": {
8288 "mounted": "/",
8289 "percent": "71"
8290 },
8291 "summary": "shell df -P / -> exit 0; percent=71, mounted=/"
8292 },
8293 {
8294 "chosen": "eq True",
8295 "depth": 0,
8296 "kind": "decide",
8297 "next": "number",
8298 "node": "ran",
8299 "observed": true,
8300 "summary": "state.df.ok = True"
8301 },
8302 {
8303 "depth": 0,
8304 "kind": "set",
8305 "next": "level",
8306 "node": "number",
8307 "observed": {
8308 "pct": 71
8309 },
8310 "summary": "pct=71"
8311 },
8312 {
8313 "chosen": "lt 80",
8314 "depth": 0,
8315 "kind": "decide",
8316 "next": "fine",
8317 "node": "level",
8318 "observed": 71,
8319 "summary": "state.pct = 71"
8320 },
8321 {
8322 "depth": 0,
8323 "kind": "done",
8324 "next": null,
8325 "node": "fine",
8326 "summary": "/ is 71% full."
8327 }
8328 ],
8329 "tree": "disk-space"
8330 },
8331 "tree": "disk-space"
8332 },
8333 "inputs": {},
8334 "intent": "how full is the disk",
8335 "tree": "disk-space"
8336 },
8337 {
8338 "expect": {
8339 "inputs": {
8340 "mount": "/data"
8341 },
8342 "reason": "disk-full",
8343 "say": "/data is 96% full.",
8344 "status": "failed",
8345 "trace": {
8346 "gaps": [],
8347 "steps": [
8348 {
8349 "depth": 0,
8350 "kind": "intent",
8351 "next": "df",
8352 "node": "intent",
8353 "observed": {
8354 "cleaned": "disk space on /data",
8355 "inputs": {
8356 "mount": "/data"
8357 },
8358 "steps": [],
8359 "trigger": "disk space on {mount:word}"
8360 },
8361 "summary": "'disk space on /data'; disk-space via 'disk space on {mount:word}' (11 literal chars)"
8362 },
8363 {
8364 "depth": 0,
8365 "kind": "observe",
8366 "next": "ran",
8367 "node": "df",
8368 "observed": {
8369 "mounted": "/data",
8370 "percent": "96"
8371 },
8372 "summary": "shell df -P /data -> exit 0; percent=96, mounted=/data"
8373 },
8374 {
8375 "chosen": "eq True",
8376 "depth": 0,
8377 "kind": "decide",
8378 "next": "number",
8379 "node": "ran",
8380 "observed": true,
8381 "summary": "state.df.ok = True"
8382 },
8383 {
8384 "depth": 0,
8385 "kind": "set",
8386 "next": "level",
8387 "node": "number",
8388 "observed": {
8389 "pct": 96
8390 },
8391 "summary": "pct=96"
8392 },
8393 {
8394 "chosen": "else",
8395 "depth": 0,
8396 "kind": "decide",
8397 "next": "full",
8398 "node": "level",
8399 "observed": 96,
8400 "summary": "state.pct = 96"
8401 },
8402 {
8403 "depth": 0,
8404 "kind": "fail",
8405 "next": null,
8406 "node": "full",
8407 "summary": "disk-full: /data is 96% full."
8408 }
8409 ],
8410 "tree": "disk-space"
8411 },
8412 "tree": "disk-space"
8413 },
8414 "inputs": {
8415 "mount": "/data"
8416 },
8417 "intent": "disk space on /data",
8418 "tree": "disk-space"
8419 },
8420 {
8421 "expect": {
8422 "inputs": {
8423 "mount": "/nowhere"
8424 },
8425 "reason": "df-failed",
8426 "say": "df could not measure /nowhere: df: /nowhere: No such file or directory",
8427 "status": "failed",
8428 "trace": {
8429 "gaps": [],
8430 "steps": [
8431 {
8432 "depth": 0,
8433 "kind": "intent",
8434 "next": "df",
8435 "node": "intent",
8436 "observed": {
8437 "cleaned": "disk space on /nowhere",
8438 "inputs": {
8439 "mount": "/nowhere"
8440 },
8441 "steps": [],
8442 "trigger": "disk space on {mount:word}"
8443 },
8444 "summary": "'disk space on /nowhere'; disk-space via 'disk space on {mount:word}' (11 literal chars)"
8445 },
8446 {
8447 "depth": 0,
8448 "kind": "observe",
8449 "next": "ran",
8450 "node": "df",
8451 "observed": {
8452 "mounted": null,
8453 "percent": null
8454 },
8455 "summary": "shell df -P /nowhere -> exit 1; percent=None, mounted=None"
8456 },
8457 {
8458 "chosen": "else",
8459 "depth": 0,
8460 "kind": "decide",
8461 "next": "failed",
8462 "node": "ran",
8463 "observed": false,
8464 "summary": "state.df.ok = False"
8465 },
8466 {
8467 "depth": 0,
8468 "kind": "fail",
8469 "next": null,
8470 "node": "failed",
8471 "summary": "df-failed: df could not measure /nowhere: df: /nowhere: No such file or directory"
8472 }
8473 ],
8474 "tree": "disk-space"
8475 },
8476 "tree": "disk-space"
8477 },
8478 "inputs": {
8479 "mount": "/nowhere"
8480 },
8481 "intent": "disk space on /nowhere",
8482 "tree": "disk-space"
8483 },
8484 {
8485 "expect": {
8486 "inputs": {
8487 "lines": 200,
8488 "path": "/var/log/syslog"
8489 },
8490 "output": {
8491 "errors": 2,
8492 "lines": [
8493 "Oct 25 12:00:00 nas pyrtan[1200]: error: weather: open-meteo answered 503",
8494 "Oct 25 12:01:02 nas pyrtan[1200]: ERROR site-status: broken.example answered 500"
8495 ],
8496 "path": "/var/log/syslog"
8497 },
8498 "say": "2 error line(s) in the last 5 of /var/log/syslog; latest: Oct 25 12:01:02 nas pyrtan[1200]: ERROR site-status: broken.example answered 500",
8499 "status": "done",
8500 "trace": {
8501 "gaps": [],
8502 "steps": [
8503 {
8504 "depth": 0,
8505 "kind": "intent",
8506 "next": "read",
8507 "node": "intent",
8508 "observed": {
8509 "cleaned": "errors in the log",
8510 "inputs": {},
8511 "steps": [],
8512 "trigger": "errors in the log"
8513 },
8514 "summary": "'errors in the log'; log-errors via 'errors in the log' (14 literal chars)"
8515 },
8516 {
8517 "depth": 0,
8518 "kind": "observe",
8519 "next": "exists",
8520 "node": "read",
8521 "observed": {
8522 "errors": [
8523 "Oct 25 12:00:00 nas pyrtan[1200]: error: weather: open-meteo answered 503",
8524 "Oct 25 12:01:02 nas pyrtan[1200]: ERROR site-status: broken.example answered 500"
8525 ]
8526 },
8527 "summary": "file /var/log/syslog -> 48213 B; errors=['Oct 25 12:00:00 nas pyrtan[1200]: error: weather: open-meteo answered 503', '…"
8528 },
8529 {
8530 "chosen": "eq True",
8531 "depth": 0,
8532 "kind": "decide",
8533 "next": "count",
8534 "node": "exists",
8535 "observed": true,
8536 "summary": "state.log.exists = True"
8537 },
8538 {
8539 "depth": 0,
8540 "kind": "set",
8541 "next": "any",
8542 "node": "count",
8543 "observed": {
8544 "n": 2,
8545 "seen": 5
8546 },
8547 "summary": "n=2, seen=5"
8548 },
8549 {
8550 "chosen": "else",
8551 "depth": 0,
8552 "kind": "decide",
8553 "next": "some",
8554 "node": "any",
8555 "observed": 2,
8556 "summary": "state.n = 2"
8557 },
8558 {
8559 "depth": 0,
8560 "kind": "done",
8561 "next": null,
8562 "node": "some",
8563 "summary": "2 error line(s) in the last 5 of /var/log/syslog; latest: Oct 25 12:01:02 nas pyrtan[1200]: ERROR site-status: broken.example answered 500"
8564 }
8565 ],
8566 "tree": "log-errors"
8567 },
8568 "tree": "log-errors"
8569 },
8570 "inputs": {},
8571 "intent": "errors in the log",
8572 "tree": "log-errors"
8573 },
8574 {
8575 "expect": {
8576 "inputs": {
8577 "lines": 200,
8578 "path": "/var/log/quiet.log"
8579 },
8580 "output": {
8581 "errors": 0,
8582 "path": "/var/log/quiet.log"
8583 },
8584 "say": "No error lines in the last 1 of /var/log/quiet.log.",
8585 "status": "done",
8586 "trace": {
8587 "gaps": [],
8588 "steps": [
8589 {
8590 "depth": 0,
8591 "kind": "intent",
8592 "next": "read",
8593 "node": "intent",
8594 "observed": {
8595 "cleaned": "errors in /var/log/quiet.log",
8596 "inputs": {
8597 "path": "/var/log/quiet.log"
8598 },
8599 "steps": [],
8600 "trigger": "errors in {path:word}"
8601 },
8602 "summary": "'errors in /var/log/quiet.log'; log-errors via 'errors in {path:word}' (8 literal chars)"
8603 },
8604 {
8605 "depth": 0,
8606 "kind": "observe",
8607 "next": "exists",
8608 "node": "read",
8609 "observed": {
8610 "errors": []
8611 },
8612 "summary": "file /var/log/quiet.log -> 71 B; errors=[]"
8613 },
8614 {
8615 "chosen": "eq True",
8616 "depth": 0,
8617 "kind": "decide",
8618 "next": "count",
8619 "node": "exists",
8620 "observed": true,
8621 "summary": "state.log.exists = True"
8622 },
8623 {
8624 "depth": 0,
8625 "kind": "set",
8626 "next": "any",
8627 "node": "count",
8628 "observed": {
8629 "n": 0,
8630 "seen": 1
8631 },
8632 "summary": "n=0, seen=1"
8633 },
8634 {
8635 "chosen": "eq 0",
8636 "depth": 0,
8637 "kind": "decide",
8638 "next": "clean",
8639 "node": "any",
8640 "observed": 0,
8641 "summary": "state.n = 0"
8642 },
8643 {
8644 "depth": 0,
8645 "kind": "done",
8646 "next": null,
8647 "node": "clean",
8648 "summary": "No error lines in the last 1 of /var/log/quiet.log."
8649 }
8650 ],
8651 "tree": "log-errors"
8652 },
8653 "tree": "log-errors"
8654 },
8655 "inputs": {
8656 "path": "/var/log/quiet.log"
8657 },
8658 "intent": "errors in /var/log/quiet.log",
8659 "tree": "log-errors"
8660 },
8661 {
8662 "expect": {
8663 "inputs": {
8664 "lines": 200,
8665 "path": "/var/log/nothing.log"
8666 },
8667 "reason": "no-such-file",
8668 "say": "/var/log/nothing.log is not on this box.",
8669 "status": "failed",
8670 "trace": {
8671 "gaps": [],
8672 "steps": [
8673 {
8674 "depth": 0,
8675 "kind": "intent",
8676 "next": "read",
8677 "node": "intent",
8678 "observed": {
8679 "cleaned": "errors in /var/log/nothing.log",
8680 "inputs": {
8681 "path": "/var/log/nothing.log"
8682 },
8683 "steps": [],
8684 "trigger": "errors in {path:word}"
8685 },
8686 "summary": "'errors in /var/log/nothing.log'; log-errors via 'errors in {path:word}' (8 literal chars)"
8687 },
8688 {
8689 "depth": 0,
8690 "kind": "observe",
8691 "next": "exists",
8692 "node": "read",
8693 "observed": {
8694 "errors": null
8695 },
8696 "summary": "file /var/log/nothing.log -> missing; errors=None"
8697 },
8698 {
8699 "chosen": "else",
8700 "depth": 0,
8701 "kind": "decide",
8702 "next": "missing",
8703 "node": "exists",
8704 "observed": false,
8705 "summary": "state.log.exists = False"
8706 },
8707 {
8708 "depth": 0,
8709 "kind": "fail",
8710 "next": null,
8711 "node": "missing",
8712 "summary": "no-such-file: /var/log/nothing.log is not on this box."
8713 }
8714 ],
8715 "tree": "log-errors"
8716 },
8717 "tree": "log-errors"
8718 },
8719 "inputs": {
8720 "path": "/var/log/nothing.log"
8721 },
8722 "intent": "errors in /var/log/nothing.log",
8723 "tree": "log-errors"
8724 },
8725 {
8726 "expect": {
8727 "inputs": {
8728 "lines": 200,
8729 "path": "/etc/passwd"
8730 },
8731 "reason": "path-not-allowed",
8732 "say": "read: /etc/passwd is outside this tree's paths ['/var/log'].",
8733 "status": "error",
8734 "trace": {
8735 "gaps": [],
8736 "steps": [
8737 {
8738 "depth": 0,
8739 "kind": "intent",
8740 "next": "read",
8741 "node": "intent",
8742 "observed": {
8743 "cleaned": "errors in /etc/passwd",
8744 "inputs": {
8745 "path": "/etc/passwd"
8746 },
8747 "steps": [],
8748 "trigger": "errors in {path:word}"
8749 },
8750 "summary": "'errors in /etc/passwd'; log-errors via 'errors in {path:word}' (8 literal chars)"
8751 }
8752 ],
8753 "tree": "log-errors"
8754 },
8755 "tree": "log-errors"
8756 },
8757 "inputs": {
8758 "path": "/etc/passwd"
8759 },
8760 "intent": "errors in /etc/passwd",
8761 "tree": "log-errors"
8762 },
8763 {
8764 "expect": {
8765 "inputs": {
8766 "room": "kitchen"
8767 },
8768 "output": {
8769 "room": "kitchen",
8770 "temperature": 21.5,
8771 "topic": "home/kitchen/temperature"
8772 },
8773 "say": "kitchen: 21.5°C.",
8774 "status": "done",
8775 "trace": {
8776 "gaps": [],
8777 "steps": [
8778 {
8779 "depth": 0,
8780 "kind": "intent",
8781 "next": "wait",
8782 "node": "intent",
8783 "observed": {
8784 "cleaned": "temperature in the kitchen",
8785 "inputs": {
8786 "room": "kitchen"
8787 },
8788 "steps": [],
8789 "trigger": "[what's the] temperature in the {room:word}"
8790 },
8791 "summary": "'temperature in the kitchen'; room-temperature via \"[what's the] temperature in the {room:word}\" (25 literal chars)"
8792 },
8793 {
8794 "depth": 0,
8795 "kind": "observe",
8796 "next": "got",
8797 "node": "wait",
8798 "summary": "queue home/kitchen/temperature at mqtt://broker.local -> 1 message"
8799 },
8800 {
8801 "chosen": "eq True",
8802 "depth": 0,
8803 "kind": "decide",
8804 "next": "reading",
8805 "node": "got",
8806 "observed": true,
8807 "summary": "state.q.received = True"
8808 },
8809 {
8810 "chosen": "lt 26",
8811 "depth": 0,
8812 "kind": "decide",
8813 "next": "fine",
8814 "node": "reading",
8815 "observed": 21.5,
8816 "summary": "state.q.json.temperature = 21.5"
8817 },
8818 {
8819 "depth": 0,
8820 "kind": "done",
8821 "next": null,
8822 "node": "fine",
8823 "summary": "kitchen: 21.5°C."
8824 }
8825 ],
8826 "tree": "room-temperature"
8827 },
8828 "tree": "room-temperature"
8829 },
8830 "inputs": {
8831 "room": "kitchen"
8832 },
8833 "intent": "temperature in the kitchen",
8834 "tree": "room-temperature"
8835 },
8836 {
8837 "expect": {
8838 "inputs": {
8839 "room": "attic"
8840 },
8841 "output": {
8842 "room": "attic",
8843 "temperature": 31,
8844 "topic": "home/attic/temperature"
8845 },
8846 "say": "attic: 31°C, warm.",
8847 "status": "done",
8848 "trace": {
8849 "gaps": [],
8850 "steps": [
8851 {
8852 "depth": 0,
8853 "kind": "intent",
8854 "next": "wait",
8855 "node": "intent",
8856 "observed": {
8857 "cleaned": "how warm is the attic",
8858 "inputs": {
8859 "room": "attic"
8860 },
8861 "steps": [],
8862 "trigger": "how warm is the {room:word}"
8863 },
8864 "summary": "'how warm is the attic'; room-temperature via 'how warm is the {room:word}' (12 literal chars)"
8865 },
8866 {
8867 "depth": 0,
8868 "kind": "observe",
8869 "next": "got",
8870 "node": "wait",
8871 "summary": "queue home/attic/temperature at mqtt://broker.local -> 1 message"
8872 },
8873 {
8874 "chosen": "eq True",
8875 "depth": 0,
8876 "kind": "decide",
8877 "next": "reading",
8878 "node": "got",
8879 "observed": true,
8880 "summary": "state.q.received = True"
8881 },
8882 {
8883 "chosen": "gte 26",
8884 "depth": 0,
8885 "kind": "decide",
8886 "next": "warm",
8887 "node": "reading",
8888 "observed": 31,
8889 "summary": "state.q.json.temperature = 31"
8890 },
8891 {
8892 "depth": 0,
8893 "kind": "done",
8894 "next": null,
8895 "node": "warm",
8896 "summary": "attic: 31°C, warm."
8897 }
8898 ],
8899 "tree": "room-temperature"
8900 },
8901 "tree": "room-temperature"
8902 },
8903 "inputs": {
8904 "room": "attic"
8905 },
8906 "intent": "how warm is the attic",
8907 "tree": "room-temperature"
8908 },
8909 {
8910 "expect": {
8911 "inputs": {
8912 "room": "garage"
8913 },
8914 "reason": "no-reading",
8915 "say": "Nothing on home/garage/temperature within 15 s; is the sensor publishing?",
8916 "status": "failed",
8917 "trace": {
8918 "gaps": [],
8919 "steps": [
8920 {
8921 "depth": 0,
8922 "kind": "intent",
8923 "next": "wait",
8924 "node": "intent",
8925 "observed": {
8926 "cleaned": "temperature in the garage",
8927 "inputs": {
8928 "room": "garage"
8929 },
8930 "steps": [],
8931 "trigger": "[what's the] temperature in the {room:word}"
8932 },
8933 "summary": "'temperature in the garage'; room-temperature via \"[what's the] temperature in the {room:word}\" (25 literal chars)"
8934 },
8935 {
8936 "depth": 0,
8937 "kind": "observe",
8938 "next": "got",
8939 "node": "wait",
8940 "summary": "queue home/garage/temperature at mqtt://broker.local -> no message"
8941 },
8942 {
8943 "chosen": "else",
8944 "depth": 0,
8945 "kind": "decide",
8946 "next": "quiet",
8947 "node": "got",
8948 "observed": false,
8949 "summary": "state.q.received = False"
8950 },
8951 {
8952 "depth": 0,
8953 "kind": "fail",
8954 "next": null,
8955 "node": "quiet",
8956 "summary": "no-reading: Nothing on home/garage/temperature within 15 s; is the sensor publishing?"
8957 }
8958 ],
8959 "tree": "room-temperature"
8960 },
8961 "tree": "room-temperature"
8962 },
8963 "inputs": {
8964 "room": "garage"
8965 },
8966 "intent": "temperature in the garage",
8967 "tree": "room-temperature"
8968 },
8969 {
8970 "expect": {
8971 "inputs": {
8972 "room": "hall"
8973 },
8974 "output": {
8975 "gap": {
8976 "node": "reading",
8977 "observed": null,
8978 "on": "state.q.json.temperature",
8979 "tree": "room-temperature"
8980 }
8981 },
8982 "reason": "gap",
8983 "say": "reading: no branch for state.q.json.temperature = <missing>.",
8984 "status": "error",
8985 "trace": {
8986 "gaps": [
8987 {
8988 "node": "reading",
8989 "observed": null,
8990 "on": "state.q.json.temperature",
8991 "tree": "room-temperature"
8992 }
8993 ],
8994 "steps": [
8995 {
8996 "depth": 0,
8997 "kind": "intent",
8998 "next": "wait",
8999 "node": "intent",
9000 "observed": {
9001 "cleaned": "temperature in the hall",
9002 "inputs": {
9003 "room": "hall"
9004 },
9005 "steps": [],
9006 "trigger": "[what's the] temperature in the {room:word}"
9007 },
9008 "summary": "'temperature in the hall'; room-temperature via \"[what's the] temperature in the {room:word}\" (25 literal chars)"
9009 },
9010 {
9011 "depth": 0,
9012 "kind": "observe",
9013 "next": "got",
9014 "node": "wait",
9015 "summary": "queue home/hall/temperature at mqtt://broker.local -> 1 message"
9016 },
9017 {
9018 "chosen": "eq True",
9019 "depth": 0,
9020 "kind": "decide",
9021 "next": "reading",
9022 "node": "got",
9023 "observed": true,
9024 "summary": "state.q.received = True"
9025 },
9026 {
9027 "depth": 0,
9028 "kind": "decide",
9029 "next": null,
9030 "node": "reading",
9031 "summary": "state.q.json.temperature = <missing>: no branch"
9032 }
9033 ],
9034 "tree": "room-temperature"
9035 },
9036 "tree": "room-temperature"
9037 },
9038 "inputs": {
9039 "room": "hall"
9040 },
9041 "intent": "temperature in the hall",
9042 "tree": "room-temperature"
9043 },
9044 {
9045 "expect": {
9046 "inputs": {
9047 "room": "a-b"
9048 },
9049 "reason": "transport",
9050 "say": "wait: no recorded result for queue mqtt://broker.local home/a-b/temperature",
9051 "status": "error",
9052 "trace": {
9053 "gaps": [],
9054 "steps": [
9055 {
9056 "depth": 0,
9057 "kind": "observe",
9058 "next": null,
9059 "node": "wait",
9060 "summary": "queue home/a-b/temperature at mqtt://broker.local failed: no recorded result for queue mqtt://broker.local home/a-b/temperature"
9061 }
9062 ],
9063 "tree": "room-temperature"
9064 },
9065 "tree": "room-temperature"
9066 },
9067 "inputs": {
9068 "room": "a-b"
9069 },
9070 "tree": "room-temperature"
9071 },
9072 {
9073 "expect": {
9074 "inputs": {
9075 "url": "https://app.example/dashboard"
9076 },
9077 "output": {
9078 "heading": "Welcome back, Barry",
9079 "title": "Dashboard",
9080 "url": "https://app.example/dashboard"
9081 },
9082 "say": "Dashboard — Welcome back, Barry",
9083 "status": "done",
9084 "trace": {
9085 "gaps": [],
9086 "steps": [
9087 {
9088 "depth": 0,
9089 "kind": "intent",
9090 "next": "open",
9091 "node": "intent",
9092 "observed": {
9093 "cleaned": "headline of https://app.example/dashboard",
9094 "inputs": {
9095 "url": "https://app.example/dashboard"
9096 },
9097 "steps": [],
9098 "trigger": "headline of {url:url}"
9099 },
9100 "summary": "'headline of https://app.example/dashboard'; rendered-headline via 'headline of {url:url}' (10 literal chars)"
9101 },
9102 {
9103 "depth": 0,
9104 "kind": "observe",
9105 "next": "loaded",
9106 "node": "open",
9107 "observed": {
9108 "heading": "Welcome back, Barry"
9109 },
9110 "summary": "browser https://app.example/dashboard -> 200; heading=Welcome back, Barry"
9111 },
9112 {
9113 "chosen": "eq True",
9114 "depth": 0,
9115 "kind": "decide",
9116 "next": "found",
9117 "node": "loaded",
9118 "observed": true,
9119 "summary": "state.page.ok = True"
9120 },
9121 {
9122 "chosen": "exists True",
9123 "depth": 0,
9124 "kind": "decide",
9125 "next": "say",
9126 "node": "found",
9127 "observed": "Welcome back, Barry",
9128 "summary": "state.heading = Welcome back, Barry"
9129 },
9130 {
9131 "depth": 0,
9132 "kind": "done",
9133 "next": null,
9134 "node": "say",
9135 "summary": "Dashboard — Welcome back, Barry"
9136 }
9137 ],
9138 "tree": "rendered-headline"
9139 },
9140 "tree": "rendered-headline"
9141 },
9142 "inputs": {
9143 "url": "https://app.example/dashboard"
9144 },
9145 "intent": "headline of https://app.example/dashboard",
9146 "tree": "rendered-headline"
9147 },
9148 {
9149 "expect": {
9150 "inputs": {
9151 "url": "https://app.example/empty"
9152 },
9153 "reason": "no-heading",
9154 "say": "https://app.example/empty rendered without an h1.",
9155 "status": "failed",
9156 "trace": {
9157 "gaps": [],
9158 "steps": [
9159 {
9160 "depth": 0,
9161 "kind": "intent",
9162 "next": "open",
9163 "node": "intent",
9164 "observed": {
9165 "cleaned": "what does https://app.example/empty say",
9166 "inputs": {
9167 "url": "https://app.example/empty"
9168 },
9169 "steps": [],
9170 "trigger": "what does {url:url} say"
9171 },
9172 "summary": "'what does https://app.example/empty say'; rendered-headline via 'what does {url:url} say' (11 literal chars)"
9173 },
9174 {
9175 "depth": 0,
9176 "kind": "observe",
9177 "next": "loaded",
9178 "node": "open",
9179 "observed": {
9180 "heading": null
9181 },
9182 "summary": "browser https://app.example/empty -> 200; heading=None"
9183 },
9184 {
9185 "chosen": "eq True",
9186 "depth": 0,
9187 "kind": "decide",
9188 "next": "found",
9189 "node": "loaded",
9190 "observed": true,
9191 "summary": "state.page.ok = True"
9192 },
9193 {
9194 "chosen": "else",
9195 "depth": 0,
9196 "kind": "decide",
9197 "next": "none",
9198 "node": "found",
9199 "summary": "state.heading = <missing>"
9200 },
9201 {
9202 "depth": 0,
9203 "kind": "fail",
9204 "next": null,
9205 "node": "none",
9206 "summary": "no-heading: https://app.example/empty rendered without an h1."
9207 }
9208 ],
9209 "tree": "rendered-headline"
9210 },
9211 "tree": "rendered-headline"
9212 },
9213 "inputs": {
9214 "url": "https://app.example/empty"
9215 },
9216 "intent": "what does https://app.example/empty say",
9217 "tree": "rendered-headline"
9218 },
9219 {
9220 "expect": {
9221 "inputs": {
9222 "url": "https://app.example/gone"
9223 },
9224 "reason": "unexpected-response",
9225 "say": "https://app.example/gone answered 404.",
9226 "status": "failed",
9227 "trace": {
9228 "gaps": [],
9229 "steps": [
9230 {
9231 "depth": 0,
9232 "kind": "intent",
9233 "next": "open",
9234 "node": "intent",
9235 "observed": {
9236 "cleaned": "headline of https://app.example/gone",
9237 "inputs": {
9238 "url": "https://app.example/gone"
9239 },
9240 "steps": [],
9241 "trigger": "headline of {url:url}"
9242 },
9243 "summary": "'headline of https://app.example/gone'; rendered-headline via 'headline of {url:url}' (10 literal chars)"
9244 },
9245 {
9246 "depth": 0,
9247 "kind": "observe",
9248 "next": "loaded",
9249 "node": "open",
9250 "observed": {
9251 "heading": "Not found"
9252 },
9253 "summary": "browser https://app.example/gone -> 404; heading=Not found"
9254 },
9255 {
9256 "chosen": "else",
9257 "depth": 0,
9258 "kind": "decide",
9259 "next": "not-ok",
9260 "node": "loaded",
9261 "observed": false,
9262 "summary": "state.page.ok = False"
9263 },
9264 {
9265 "depth": 0,
9266 "kind": "fail",
9267 "next": null,
9268 "node": "not-ok",
9269 "summary": "unexpected-response: https://app.example/gone answered 404."
9270 }
9271 ],
9272 "tree": "rendered-headline"
9273 },
9274 "tree": "rendered-headline"
9275 },
9276 "inputs": {
9277 "url": "https://app.example/gone"
9278 },
9279 "intent": "headline of https://app.example/gone",
9280 "tree": "rendered-headline"
9281 },
9282 {
9283 "expect": {
9284 "inputs": {
9285 "device": "/dev/ttyUSB0"
9286 },
9287 "output": {
9288 "device": "/dev/ttyUSB0"
9289 },
9290 "say": "The modem on /dev/ttyUSB0 is there.",
9291 "status": "done",
9292 "trace": {
9293 "gaps": [],
9294 "steps": [
9295 {
9296 "depth": 0,
9297 "kind": "intent",
9298 "next": "at",
9299 "node": "intent",
9300 "observed": {
9301 "cleaned": "is the modem up",
9302 "inputs": {},
9303 "steps": [],
9304 "trigger": "is the modem up"
9305 },
9306 "summary": "'is the modem up'; modem-check via 'is the modem up' (12 literal chars)"
9307 },
9308 {
9309 "depth": 0,
9310 "kind": "observe",
9311 "next": "answered",
9312 "node": "at",
9313 "observed": {
9314 "answer": "OK"
9315 },
9316 "summary": "serial /dev/ttyUSB0 -> 4 B; answer=OK"
9317 },
9318 {
9319 "chosen": "eq 'OK'",
9320 "depth": 0,
9321 "kind": "decide",
9322 "next": "yes",
9323 "node": "answered",
9324 "observed": "OK",
9325 "summary": "state.answer = OK"
9326 },
9327 {
9328 "depth": 0,
9329 "kind": "done",
9330 "next": null,
9331 "node": "yes",
9332 "summary": "The modem on /dev/ttyUSB0 is there."
9333 }
9334 ],
9335 "tree": "modem-check"
9336 },
9337 "tree": "modem-check"
9338 },
9339 "inputs": {},
9340 "intent": "is the modem up",
9341 "tree": "modem-check"
9342 },
9343 {
9344 "expect": {
9345 "inputs": {
9346 "device": "/dev/ttyUSB1"
9347 },
9348 "reason": "transport",
9349 "say": "at: /dev/ttyUSB1: no answer within 3 s",
9350 "status": "error",
9351 "trace": {
9352 "gaps": [],
9353 "steps": [
9354 {
9355 "depth": 0,
9356 "kind": "intent",
9357 "next": "at",
9358 "node": "intent",
9359 "observed": {
9360 "cleaned": "is the modem on /dev/ttyUSB1 up",
9361 "inputs": {
9362 "device": "/dev/ttyUSB1"
9363 },
9364 "steps": [],
9365 "trigger": "is the modem on {device:word} up"
9366 },
9367 "summary": "'is the modem on /dev/ttyUSB1 up'; modem-check via 'is the modem on {device:word} up' (14 literal chars)"
9368 },
9369 {
9370 "depth": 0,
9371 "kind": "observe",
9372 "next": null,
9373 "node": "at",
9374 "summary": "serial /dev/ttyUSB1 failed: /dev/ttyUSB1: no answer within 3 s"
9375 }
9376 ],
9377 "tree": "modem-check"
9378 },
9379 "tree": "modem-check"
9380 },
9381 "inputs": {
9382 "device": "/dev/ttyUSB1"
9383 },
9384 "intent": "is the modem on /dev/ttyUSB1 up",
9385 "tree": "modem-check"
9386 },
9387 {
9388 "expect": {
9389 "inputs": {
9390 "device": "/dev/ttyACM0"
9391 },
9392 "reason": "modem-error",
9393 "say": "The modem on /dev/ttyACM0 answered ERROR.",
9394 "status": "failed",
9395 "trace": {
9396 "gaps": [],
9397 "steps": [
9398 {
9399 "depth": 0,
9400 "kind": "intent",
9401 "next": "at",
9402 "node": "intent",
9403 "observed": {
9404 "cleaned": "is the modem on /dev/ttyACM0 up",
9405 "inputs": {
9406 "device": "/dev/ttyACM0"
9407 },
9408 "steps": [],
9409 "trigger": "is the modem on {device:word} up"
9410 },
9411 "summary": "'is the modem on /dev/ttyACM0 up'; modem-check via 'is the modem on {device:word} up' (14 literal chars)"
9412 },
9413 {
9414 "depth": 0,
9415 "kind": "observe",
9416 "next": "answered",
9417 "node": "at",
9418 "observed": {
9419 "answer": "ERROR"
9420 },
9421 "summary": "serial /dev/ttyACM0 -> 16 B; answer=ERROR"
9422 },
9423 {
9424 "chosen": "eq 'ERROR'",
9425 "depth": 0,
9426 "kind": "decide",
9427 "next": "error",
9428 "node": "answered",
9429 "observed": "ERROR",
9430 "summary": "state.answer = ERROR"
9431 },
9432 {
9433 "depth": 0,
9434 "kind": "fail",
9435 "next": null,
9436 "node": "error",
9437 "summary": "modem-error: The modem on /dev/ttyACM0 answered ERROR."
9438 }
9439 ],
9440 "tree": "modem-check"
9441 },
9442 "tree": "modem-check"
9443 },
9444 "inputs": {
9445 "device": "/dev/ttyACM0"
9446 },
9447 "intent": "is the modem on /dev/ttyACM0 up",
9448 "tree": "modem-check"
9449 },
9450 {
9451 "expect": {
9452 "inputs": {
9453 "device": "/dev/ttyS0"
9454 },
9455 "reason": "device-not-allowed",
9456 "say": "at: /dev/ttyS0 is outside this tree's devices ['/dev/ttyUSB*', '/dev/ttyACM*'].",
9457 "status": "error",
9458 "trace": {
9459 "gaps": [],
9460 "steps": [
9461 {
9462 "depth": 0,
9463 "kind": "intent",
9464 "next": "at",
9465 "node": "intent",
9466 "observed": {
9467 "cleaned": "is the modem on /dev/ttyS0 up",
9468 "inputs": {
9469 "device": "/dev/ttyS0"
9470 },
9471 "steps": [],
9472 "trigger": "is the modem on {device:word} up"
9473 },
9474 "summary": "'is the modem on /dev/ttyS0 up'; modem-check via 'is the modem on {device:word} up' (14 literal chars)"
9475 }
9476 ],
9477 "tree": "modem-check"
9478 },
9479 "tree": "modem-check"
9480 },
9481 "inputs": {
9482 "device": "/dev/ttyS0"
9483 },
9484 "intent": "is the modem on /dev/ttyS0 up",
9485 "tree": "modem-check"
9486 },
9487 {
9488 "expect": {
9489 "inputs": {
9490 "topic": "how"
9491 },
9492 "output": {
9493 "topic": "how"
9494 },
9495 "say": "Deterministic, thanks: the same question takes the same path every time.",
9496 "status": "done",
9497 "trace": {
9498 "gaps": [],
9499 "steps": [
9500 {
9501 "depth": 0,
9502 "kind": "intent",
9503 "next": "topic",
9504 "node": "intent",
9505 "observed": {
9506 "cleaned": "how are you today",
9507 "inputs": {
9508 "topic": "how"
9509 },
9510 "steps": [],
9511 "trigger": "how are you [doing|today]"
9512 },
9513 "summary": "'how are you today?' -> 'how are you today'; small-talk via 'how are you [doing|today]' (19 literal chars)"
9514 },
9515 {
9516 "chosen": "eq 'how'",
9517 "depth": 0,
9518 "kind": "decide",
9519 "next": "how",
9520 "node": "topic",
9521 "observed": "how",
9522 "summary": "inputs.topic = how"
9523 },
9524 {
9525 "depth": 0,
9526 "kind": "done",
9527 "next": null,
9528 "node": "how",
9529 "summary": "Deterministic, thanks: the same question takes the same path every time."
9530 }
9531 ],
9532 "tree": "small-talk"
9533 },
9534 "tree": "small-talk"
9535 },
9536 "inputs": {
9537 "topic": "how"
9538 },
9539 "intent": "how are you today?",
9540 "tree": "small-talk"
9541 },
9542 {
9543 "expect": {
9544 "inputs": {
9545 "topic": "who"
9546 },
9547 "output": {
9548 "topic": "who"
9549 },
9550 "say": "A bot: a set of Pyrtan trees on this network, not a person and not a model. Every answer I give comes with the exact path through the tree that produced it.",
9551 "status": "done",
9552 "trace": {
9553 "gaps": [],
9554 "steps": [
9555 {
9556 "depth": 0,
9557 "kind": "intent",
9558 "next": "topic",
9559 "node": "intent",
9560 "observed": {
9561 "cleaned": "are you a bot",
9562 "inputs": {
9563 "topic": "who"
9564 },
9565 "steps": [],
9566 "trigger": "are you (a bot|human|real|a person)"
9567 },
9568 "summary": "'are you a bot'; small-talk via 'are you (a bot|human|real|a person)' (26 literal chars)"
9569 },
9570 {
9571 "chosen": "eq 'who'",
9572 "depth": 0,
9573 "kind": "decide",
9574 "next": "who",
9575 "node": "topic",
9576 "observed": "who",
9577 "summary": "inputs.topic = who"
9578 },
9579 {
9580 "depth": 0,
9581 "kind": "done",
9582 "next": null,
9583 "node": "who",
9584 "summary": "A bot: a set of Pyrtan trees on this network, not a person and not a model. Every answer I give comes with the exact path through the tree that produced it."
9585 }
9586 ],
9587 "tree": "small-talk"
9588 },
9589 "tree": "small-talk"
9590 },
9591 "inputs": {
9592 "topic": "who"
9593 },
9594 "intent": "are you a bot",
9595 "tree": "small-talk"
9596 },
9597 {
9598 "expect": {
9599 "inputs": {
9600 "topic": "bye"
9601 },
9602 "output": {
9603 "topic": "bye"
9604 },
9605 "say": "Bye. Press New to meet someone else.",
9606 "status": "done",
9607 "trace": {
9608 "gaps": [],
9609 "steps": [
9610 {
9611 "depth": 0,
9612 "kind": "intent",
9613 "next": "topic",
9614 "node": "intent",
9615 "observed": {
9616 "cleaned": "goodbye",
9617 "inputs": {
9618 "topic": "bye"
9619 },
9620 "steps": [],
9621 "trigger": "(bye|goodbye|see you|see ya)"
9622 },
9623 "summary": "'goodbye'; small-talk via '(bye|goodbye|see you|see ya)' (21 literal chars)"
9624 },
9625 {
9626 "chosen": "eq 'bye'",
9627 "depth": 0,
9628 "kind": "decide",
9629 "next": "bye",
9630 "node": "topic",
9631 "observed": "bye",
9632 "summary": "inputs.topic = bye"
9633 },
9634 {
9635 "depth": 0,
9636 "kind": "done",
9637 "next": null,
9638 "node": "bye",
9639 "summary": "Bye. Press New to meet someone else."
9640 }
9641 ],
9642 "tree": "small-talk"
9643 },
9644 "tree": "small-talk"
9645 },
9646 "inputs": {
9647 "topic": "bye"
9648 },
9649 "intent": "goodbye",
9650 "tree": "small-talk"
9651 },
9652 {
9653 "expect": {
9654 "inputs": {
9655 "topic": "Ursula K Le Guin"
9656 },
9657 "output": {
9658 "summary": "Ursula Kroeber Le Guin was an American author. She is best known for her works of speculative fiction.",
9659 "title": "Ursula K. Le Guin",
9660 "url": "https://en.wikipedia.org/wiki/Ursula_K._Le_Guin"
9661 },
9662 "say": "Ursula K. Le Guin: Ursula Kroeber Le Guin was an American author. She is best known for her works of speculative fiction. https://en.wikipedia.org/wiki/Ursula_K._Le_Guin",
9663 "status": "done",
9664 "trace": {
9665 "gaps": [],
9666 "steps": [
9667 {
9668 "depth": 0,
9669 "kind": "intent",
9670 "next": "fetch",
9671 "node": "intent",
9672 "observed": {
9673 "cleaned": "tell me about Ursula K Le Guin",
9674 "inputs": {
9675 "topic": "Ursula K Le Guin"
9676 },
9677 "steps": [],
9678 "trigger": "tell me about {topic}"
9679 },
9680 "summary": "'tell me about Ursula K. Le Guin' -> 'tell me about Ursula K Le Guin'; wikipedia via 'tell me about {topic}' (11 literal chars)"
9681 },
9682 {
9683 "depth": 0,
9684 "kind": "observe",
9685 "next": "status",
9686 "node": "fetch",
9687 "observed": {
9688 "kind": "standard",
9689 "summary": "Ursula Kroeber Le Guin was an American author. She is best known for her works of speculative fiction.",
9690 "title": "Ursula K. Le Guin",
9691 "url": "https://en.wikipedia.org/wiki/Ursula_K._Le_Guin"
9692 },
9693 "summary": "GET https://en.wikipedia.org/api/rest_v1/page/summary/Ursula%20K%20Le%20Guin -> 200; kind=standard, title=Ursula K. Le Guin, summary=Ursula Kroeber Le Guin was an American author. She is best known for her works …, url=https://en.wikipedia.org/wiki/Ursula_K._Le_Guin"
9694 },
9695 {
9696 "chosen": "eq 200",
9697 "depth": 0,
9698 "kind": "decide",
9699 "next": "kind",
9700 "node": "status",
9701 "observed": 200,
9702 "summary": "state.resp.status = 200"
9703 },
9704 {
9705 "chosen": "exists True",
9706 "depth": 0,
9707 "kind": "decide",
9708 "next": "reply",
9709 "node": "kind",
9710 "observed": "standard",
9711 "summary": "state.kind = standard"
9712 },
9713 {
9714 "depth": 0,
9715 "kind": "done",
9716 "next": null,
9717 "node": "reply",
9718 "summary": "Ursula K. Le Guin: Ursula Kroeber Le Guin was an American author. She is best known for her works of speculative fiction. https://en.wikipedia.org/wiki/Ursula_K._Le_Guin"
9719 }
9720 ],
9721 "tree": "wikipedia"
9722 },
9723 "tree": "wikipedia"
9724 },
9725 "inputs": {
9726 "topic": "Ursula K Le Guin"
9727 },
9728 "intent": "tell me about Ursula K. Le Guin",
9729 "tree": "wikipedia"
9730 },
9731 {
9732 "expect": {
9733 "inputs": {
9734 "topic": "Mercury"
9735 },
9736 "reason": "ambiguous",
9737 "say": "Mercury could mean several things on Wikipedia; say which: https://en.wikipedia.org/wiki/Mercury",
9738 "status": "failed",
9739 "trace": {
9740 "gaps": [],
9741 "steps": [
9742 {
9743 "depth": 0,
9744 "kind": "intent",
9745 "next": "fetch",
9746 "node": "intent",
9747 "observed": {
9748 "cleaned": "wiki Mercury",
9749 "inputs": {
9750 "topic": "Mercury"
9751 },
9752 "steps": [],
9753 "trigger": "(wikipedia|wiki) {topic}"
9754 },
9755 "summary": "'wiki Mercury'; wikipedia via '(wikipedia|wiki) {topic}' (13 literal chars)"
9756 },
9757 {
9758 "depth": 0,
9759 "kind": "observe",
9760 "next": "status",
9761 "node": "fetch",
9762 "observed": {
9763 "kind": "disambiguation",
9764 "summary": "Mercury most commonly refers to:Mercury (planet), the closest planet to the Sun\n",
9765 "title": "Mercury",
9766 "url": "https://en.wikipedia.org/wiki/Mercury"
9767 },
9768 "summary": "GET https://en.wikipedia.org/api/rest_v1/page/summary/Mercury -> 200; kind=disambiguation, title=Mercury, summary=Mercury most commonly refers to:Mercury (planet), the closest planet to the Sun\n, url=https://en.wikipedia.org/wiki/Mercury"
9769 },
9770 {
9771 "chosen": "eq 200",
9772 "depth": 0,
9773 "kind": "decide",
9774 "next": "kind",
9775 "node": "status",
9776 "observed": 200,
9777 "summary": "state.resp.status = 200"
9778 },
9779 {
9780 "chosen": "eq 'disambiguation'",
9781 "depth": 0,
9782 "kind": "decide",
9783 "next": "ambiguous",
9784 "node": "kind",
9785 "observed": "disambiguation",
9786 "summary": "state.kind = disambiguation"
9787 },
9788 {
9789 "depth": 0,
9790 "kind": "fail",
9791 "next": null,
9792 "node": "ambiguous",
9793 "summary": "ambiguous: Mercury could mean several things on Wikipedia; say which: https://en.wikipedia.org/wiki/Mercury"
9794 }
9795 ],
9796 "tree": "wikipedia"
9797 },
9798 "tree": "wikipedia"
9799 },
9800 "inputs": {
9801 "topic": "Mercury"
9802 },
9803 "intent": "wiki Mercury",
9804 "tree": "wikipedia"
9805 },
9806 {
9807 "expect": {
9808 "inputs": {
9809 "topic": "Xyzzyplugh Notaperson"
9810 },
9811 "reason": "no-article",
9812 "say": "Wikipedia has no article called Xyzzyplugh Notaperson.",
9813 "status": "failed",
9814 "trace": {
9815 "gaps": [],
9816 "steps": [
9817 {
9818 "depth": 0,
9819 "kind": "intent",
9820 "next": "fetch",
9821 "node": "intent",
9822 "observed": {
9823 "cleaned": "who is Xyzzyplugh Notaperson",
9824 "inputs": {
9825 "topic": "Xyzzyplugh Notaperson"
9826 },
9827 "steps": [],
9828 "trigger": "who (is|was) {topic}"
9829 },
9830 "summary": "'who is Xyzzyplugh Notaperson'; wikipedia via 'who (is|was) {topic}' (8 literal chars)"
9831 },
9832 {
9833 "depth": 0,
9834 "kind": "observe",
9835 "next": "status",
9836 "node": "fetch",
9837 "observed": {
9838 "kind": "Internal error",
9839 "summary": null,
9840 "title": null,
9841 "url": null
9842 },
9843 "summary": "GET https://en.wikipedia.org/api/rest_v1/page/summary/Xyzzyplugh%20Notaperson -> 404; kind=Internal error, title=None, summary=None, url=None"
9844 },
9845 {
9846 "chosen": "eq 404",
9847 "depth": 0,
9848 "kind": "decide",
9849 "next": "no-article",
9850 "node": "status",
9851 "observed": 404,
9852 "summary": "state.resp.status = 404"
9853 },
9854 {
9855 "depth": 0,
9856 "kind": "fail",
9857 "next": null,
9858 "node": "no-article",
9859 "summary": "no-article: Wikipedia has no article called Xyzzyplugh Notaperson."
9860 }
9861 ],
9862 "tree": "wikipedia"
9863 },
9864 "tree": "wikipedia"
9865 },
9866 "inputs": {
9867 "topic": "Xyzzyplugh Notaperson"
9868 },
9869 "intent": "who is Xyzzyplugh Notaperson",
9870 "tree": "wikipedia"
9871 },
9872 {
9873 "expect": {
9874 "inputs": {
9875 "topic": "Alan Turing"
9876 },
9877 "reason": "rate-limited",
9878 "say": "Wikipedia is asking for fewer requests (429); try again in a minute.",
9879 "status": "failed",
9880 "trace": {
9881 "gaps": [],
9882 "steps": [
9883 {
9884 "depth": 0,
9885 "kind": "intent",
9886 "next": "fetch",
9887 "node": "intent",
9888 "observed": {
9889 "cleaned": "who was Alan Turing",
9890 "inputs": {
9891 "topic": "Alan Turing"
9892 },
9893 "steps": [],
9894 "trigger": "who (is|was) {topic}"
9895 },
9896 "summary": "'who was Alan Turing'; wikipedia via 'who (is|was) {topic}' (8 literal chars)"
9897 },
9898 {
9899 "depth": 0,
9900 "kind": "observe",
9901 "next": "status",
9902 "node": "fetch",
9903 "observed": {
9904 "kind": null,
9905 "summary": null,
9906 "title": null,
9907 "url": null
9908 },
9909 "summary": "GET https://en.wikipedia.org/api/rest_v1/page/summary/Alan%20Turing -> 429; kind=None, title=None, summary=None, url=None"
9910 },
9911 {
9912 "chosen": "eq 429",
9913 "depth": 0,
9914 "kind": "decide",
9915 "next": "rate-limited",
9916 "node": "status",
9917 "observed": 429,
9918 "summary": "state.resp.status = 429"
9919 },
9920 {
9921 "depth": 0,
9922 "kind": "fail",
9923 "next": null,
9924 "node": "rate-limited",
9925 "summary": "rate-limited: Wikipedia is asking for fewer requests (429); try again in a minute."
9926 }
9927 ],
9928 "tree": "wikipedia"
9929 },
9930 "tree": "wikipedia"
9931 },
9932 "inputs": {
9933 "topic": "Alan Turing"
9934 },
9935 "intent": "who was Alan Turing",
9936 "tree": "wikipedia"
9937 },
9938 {
9939 "expect": {
9940 "inputs": {
9941 "word": "serendipity"
9942 },
9943 "output": {
9944 "definition": "A combination of events which have come together by chance to make a surprisingly good or wonderful outcome.",
9945 "part_of_speech": "noun",
9946 "phonetic": "/ˌsɛ.ɹən.ˈdɪ.pɪ.ti/",
9947 "word": "serendipity"
9948 },
9949 "say": "serendipity (noun): A combination of events which have come together by chance to make a surprisingly good or wonderful outcome.",
9950 "status": "done",
9951 "trace": {
9952 "gaps": [],
9953 "steps": [
9954 {
9955 "depth": 0,
9956 "kind": "intent",
9957 "next": "fetch",
9958 "node": "intent",
9959 "observed": {
9960 "cleaned": "what does serendipity mean",
9961 "inputs": {
9962 "word": "serendipity"
9963 },
9964 "steps": [],
9965 "trigger": "what does {word:word} mean"
9966 },
9967 "summary": "'what does serendipity mean'; define via 'what does {word:word} mean' (12 literal chars)"
9968 },
9969 {
9970 "depth": 0,
9971 "kind": "observe",
9972 "next": "status",
9973 "node": "fetch",
9974 "observed": {
9975 "definition": "A combination of events which have come together by chance to make a surprisingly good or wonderful outcome.",
9976 "part": "noun",
9977 "phonetic": "/ˌsɛ.ɹən.ˈdɪ.pɪ.ti/",
9978 "word": "serendipity"
9979 },
9980 "summary": "GET https://api.dictionaryapi.dev/api/v2/entries/en/serendipity -> 200; word=serendipity, phonetic=/ˌsɛ.ɹən.ˈdɪ.pɪ.ti/, part=noun, definition=A combination of events which have come together by chance to make a surprising…"
9981 },
9982 {
9983 "chosen": "eq 200",
9984 "depth": 0,
9985 "kind": "decide",
9986 "next": "has-definition",
9987 "node": "status",
9988 "observed": 200,
9989 "summary": "state.resp.status = 200"
9990 },
9991 {
9992 "chosen": "exists True",
9993 "depth": 0,
9994 "kind": "decide",
9995 "next": "reply",
9996 "node": "has-definition",
9997 "observed": "A combination of events which have come together by chance to make a surprisingly good or wonderful outcome.",
9998 "summary": "state.definition = A combination of events which have come together by chance to make a surprising…"
9999 },
10000 {
10001 "depth": 0,
10002 "kind": "done",
10003 "next": null,
10004 "node": "reply",
10005 "summary": "serendipity (noun): A combination of events which have come together by chance to make a surprisingly good or wonderful outcome."
10006 }
10007 ],
10008 "tree": "define"
10009 },
10010 "tree": "define"
10011 },
10012 "inputs": {
10013 "word": "serendipity"
10014 },
10015 "intent": "what does serendipity mean",
10016 "tree": "define"
10017 },
10018 {
10019 "expect": {
10020 "inputs": {
10021 "word": "zzzqx"
10022 },
10023 "reason": "unknown-word",
10024 "say": "The dictionary has no entry for zzzqx.",
10025 "status": "failed",
10026 "trace": {
10027 "gaps": [],
10028 "steps": [
10029 {
10030 "depth": 0,
10031 "kind": "intent",
10032 "next": "fetch",
10033 "node": "intent",
10034 "observed": {
10035 "cleaned": "define zzzqx",
10036 "inputs": {
10037 "word": "zzzqx"
10038 },
10039 "steps": [],
10040 "trigger": "define {word:word}"
10041 },
10042 "summary": "'define zzzqx'; define via 'define {word:word}' (6 literal chars)"
10043 },
10044 {
10045 "depth": 0,
10046 "kind": "observe",
10047 "next": "status",
10048 "node": "fetch",
10049 "observed": {
10050 "definition": null,
10051 "part": null,
10052 "phonetic": null,
10053 "word": null
10054 },
10055 "summary": "GET https://api.dictionaryapi.dev/api/v2/entries/en/zzzqx -> 404; word=None, phonetic=None, part=None, definition=None"
10056 },
10057 {
10058 "chosen": "eq 404",
10059 "depth": 0,
10060 "kind": "decide",
10061 "next": "unknown-word",
10062 "node": "status",
10063 "observed": 404,
10064 "summary": "state.resp.status = 404"
10065 },
10066 {
10067 "depth": 0,
10068 "kind": "fail",
10069 "next": null,
10070 "node": "unknown-word",
10071 "summary": "unknown-word: The dictionary has no entry for zzzqx."
10072 }
10073 ],
10074 "tree": "define"
10075 },
10076 "tree": "define"
10077 },
10078 "inputs": {
10079 "word": "zzzqx"
10080 },
10081 "intent": "define zzzqx",
10082 "tree": "define"
10083 },
10084 {
10085 "expect": {
10086 "inputs": {
10087 "place": "Oslo"
10088 },
10089 "output": {
10090 "country": "Norway",
10091 "date": "2026-10-28",
10092 "place": "Oslo",
10093 "sunrise": "07:28",
10094 "sunset": "16:32",
10095 "timezone": "Europe/Oslo"
10096 },
10097 "say": "Oslo, Norway, 2026-10-28: sunrise 07:28, sunset 16:32 (Europe/Oslo).",
10098 "status": "done",
10099 "trace": {
10100 "gaps": [],
10101 "steps": [
10102 {
10103 "depth": 0,
10104 "kind": "intent",
10105 "next": "geocode",
10106 "node": "intent",
10107 "observed": {
10108 "cleaned": "when is sunset in Oslo",
10109 "inputs": {
10110 "place": "Oslo"
10111 },
10112 "steps": [],
10113 "trigger": "[when is] (sunrise|sunset) in {place}"
10114 },
10115 "summary": "'when is sunset in Oslo'; sun via '[when is] (sunrise|sunset) in {place}' (21 literal chars)"
10116 },
10117 {
10118 "depth": 0,
10119 "kind": "observe",
10120 "next": "found",
10121 "node": "geocode",
10122 "observed": {
10123 "country": "Norway",
10124 "hit": {
10125 "country": "Norway",
10126 "latitude": 59.91,
10127 "longitude": 10.75,
10128 "name": "Oslo"
10129 },
10130 "lat": 59.91,
10131 "lon": 10.75,
10132 "name": "Oslo"
10133 },
10134 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Oslo&count=1&language=en&format=json -> 200; hit={'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}, lat=59.91, lon=10.75, name=Oslo, country=Norway"
10135 },
10136 {
10137 "chosen": "exists True",
10138 "depth": 0,
10139 "kind": "decide",
10140 "next": "times",
10141 "node": "found",
10142 "observed": {
10143 "country": "Norway",
10144 "latitude": 59.91,
10145 "longitude": 10.75,
10146 "name": "Oslo"
10147 },
10148 "summary": "state.hit = {'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}"
10149 },
10150 {
10151 "depth": 0,
10152 "kind": "observe",
10153 "next": "have-times",
10154 "node": "times",
10155 "observed": {
10156 "day": "2026-10-28",
10157 "rise": "07:28",
10158 "set": "16:32",
10159 "zone": "Europe/Oslo"
10160 },
10161 "summary": "GET https://api.open-meteo.com/v1/forecast?latitude=59.91&longitude=10.75&daily=sunrise,sunset&timezone=auto&forecast_days=1 -> 200; day=2026-10-28, rise=07:28, set=16:32, zone=Europe/Oslo"
10162 },
10163 {
10164 "chosen": "exists True",
10165 "depth": 0,
10166 "kind": "decide",
10167 "next": "reply",
10168 "node": "have-times",
10169 "observed": "07:28",
10170 "summary": "state.rise = 07:28"
10171 },
10172 {
10173 "depth": 0,
10174 "kind": "done",
10175 "next": null,
10176 "node": "reply",
10177 "summary": "Oslo, Norway, 2026-10-28: sunrise 07:28, sunset 16:32 (Europe/Oslo)."
10178 }
10179 ],
10180 "tree": "sun"
10181 },
10182 "tree": "sun"
10183 },
10184 "inputs": {
10185 "place": "Oslo"
10186 },
10187 "intent": "when is sunset in Oslo",
10188 "tree": "sun"
10189 },
10190 {
10191 "expect": {
10192 "inputs": {
10193 "place": "Longyearbyen"
10194 },
10195 "reason": "no-times",
10196 "say": "Open-Meteo gave no sunrise for Longyearbyen today (status 200); near the poles the sun may not rise or set at all.",
10197 "status": "failed",
10198 "trace": {
10199 "gaps": [],
10200 "steps": [
10201 {
10202 "depth": 0,
10203 "kind": "intent",
10204 "next": "geocode",
10205 "node": "intent",
10206 "observed": {
10207 "cleaned": "sunrise in Longyearbyen",
10208 "inputs": {
10209 "place": "Longyearbyen"
10210 },
10211 "steps": [],
10212 "trigger": "[when is] (sunrise|sunset) in {place}"
10213 },
10214 "summary": "'sunrise in Longyearbyen'; sun via '[when is] (sunrise|sunset) in {place}' (21 literal chars)"
10215 },
10216 {
10217 "depth": 0,
10218 "kind": "observe",
10219 "next": "found",
10220 "node": "geocode",
10221 "observed": {
10222 "country": "Svalbard and Jan Mayen",
10223 "hit": {
10224 "country": "Svalbard and Jan Mayen",
10225 "latitude": 78.22,
10226 "longitude": 15.65,
10227 "name": "Longyearbyen"
10228 },
10229 "lat": 78.22,
10230 "lon": 15.65,
10231 "name": "Longyearbyen"
10232 },
10233 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Longyearbyen&count=1&language=en&format=json -> 200; hit={'name': 'Longyearbyen', 'country': 'Svalbard and Jan Mayen', 'latitude': 78.22…, lat=78.22, lon=15.65, name=Longyearbyen, country=Svalbard and Jan Mayen"
10234 },
10235 {
10236 "chosen": "exists True",
10237 "depth": 0,
10238 "kind": "decide",
10239 "next": "times",
10240 "node": "found",
10241 "observed": {
10242 "country": "Svalbard and Jan Mayen",
10243 "latitude": 78.22,
10244 "longitude": 15.65,
10245 "name": "Longyearbyen"
10246 },
10247 "summary": "state.hit = {'name': 'Longyearbyen', 'country': 'Svalbard and Jan Mayen', 'latitude': 78.22…"
10248 },
10249 {
10250 "depth": 0,
10251 "kind": "observe",
10252 "next": "have-times",
10253 "node": "times",
10254 "observed": {
10255 "day": "2026-12-21",
10256 "rise": null,
10257 "set": null,
10258 "zone": "Arctic/Longyearbyen"
10259 },
10260 "summary": "GET https://api.open-meteo.com/v1/forecast?latitude=78.22&longitude=15.65&daily=sunrise,sunset&timezone=auto&forecast_days=1 -> 200; day=2026-12-21, rise=None, set=None, zone=Arctic/Longyearbyen"
10261 },
10262 {
10263 "chosen": "else",
10264 "depth": 0,
10265 "kind": "decide",
10266 "next": "no-times",
10267 "node": "have-times",
10268 "summary": "state.rise = <missing>"
10269 },
10270 {
10271 "depth": 0,
10272 "kind": "fail",
10273 "next": null,
10274 "node": "no-times",
10275 "summary": "no-times: Open-Meteo gave no sunrise for Longyearbyen today (status 200); near the poles the sun may not rise or set at all."
10276 }
10277 ],
10278 "tree": "sun"
10279 },
10280 "tree": "sun"
10281 },
10282 "inputs": {
10283 "place": "Longyearbyen"
10284 },
10285 "intent": "sunrise in Longyearbyen",
10286 "tree": "sun"
10287 },
10288 {
10289 "expect": {
10290 "inputs": {
10291 "place": "Oslo"
10292 },
10293 "output": {
10294 "aqi": 21,
10295 "band": "fair",
10296 "country": "Norway",
10297 "place": "Oslo",
10298 "pm10": 10.2,
10299 "pm2_5": 3.8
10300 },
10301 "say": "Oslo, Norway: air quality fair (European AQI 21), PM2.5 3.8 µg/m³, PM10 10.2 µg/m³.",
10302 "status": "done",
10303 "trace": {
10304 "gaps": [],
10305 "steps": [
10306 {
10307 "depth": 0,
10308 "kind": "intent",
10309 "next": "geocode",
10310 "node": "intent",
10311 "observed": {
10312 "cleaned": "air quality in Oslo",
10313 "inputs": {
10314 "place": "Oslo"
10315 },
10316 "steps": [],
10317 "trigger": "[what's the] air quality (in|for) {place}"
10318 },
10319 "summary": "'air quality in Oslo'; air-quality via \"[what's the] air quality (in|for) {place}\" (24 literal chars)"
10320 },
10321 {
10322 "depth": 0,
10323 "kind": "observe",
10324 "next": "found",
10325 "node": "geocode",
10326 "observed": {
10327 "country": "Norway",
10328 "hit": {
10329 "country": "Norway",
10330 "latitude": 59.91,
10331 "longitude": 10.75,
10332 "name": "Oslo"
10333 },
10334 "lat": 59.91,
10335 "lon": 10.75,
10336 "name": "Oslo"
10337 },
10338 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Oslo&count=1&language=en&format=json -> 200; hit={'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}, lat=59.91, lon=10.75, name=Oslo, country=Norway"
10339 },
10340 {
10341 "chosen": "exists True",
10342 "depth": 0,
10343 "kind": "decide",
10344 "next": "air",
10345 "node": "found",
10346 "observed": {
10347 "country": "Norway",
10348 "latitude": 59.91,
10349 "longitude": 10.75,
10350 "name": "Oslo"
10351 },
10352 "summary": "state.hit = {'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}"
10353 },
10354 {
10355 "depth": 0,
10356 "kind": "observe",
10357 "next": "band",
10358 "node": "air",
10359 "observed": {
10360 "aqi": 21,
10361 "pm10": 10.2,
10362 "pm25": 3.8
10363 },
10364 "summary": "GET https://air-quality-api.open-meteo.com/v1/air-quality?latitude=59.91&longitude=10.75&current=european_aqi,pm2_5,pm10 -> 200; aqi=21, pm25=3.8, pm10=10.2"
10365 },
10366 {
10367 "chosen": "lte 40",
10368 "depth": 0,
10369 "kind": "decide",
10370 "next": "fair",
10371 "node": "band",
10372 "observed": 21,
10373 "summary": "state.aqi = 21"
10374 },
10375 {
10376 "depth": 0,
10377 "kind": "set",
10378 "next": "reply",
10379 "node": "fair",
10380 "observed": {
10381 "band": "fair"
10382 },
10383 "summary": "band=fair"
10384 },
10385 {
10386 "depth": 0,
10387 "kind": "done",
10388 "next": null,
10389 "node": "reply",
10390 "summary": "Oslo, Norway: air quality fair (European AQI 21), PM2.5 3.8 µg/m³, PM10 10.2 µg/m³."
10391 }
10392 ],
10393 "tree": "air-quality"
10394 },
10395 "tree": "air-quality"
10396 },
10397 "inputs": {
10398 "place": "Oslo"
10399 },
10400 "intent": "air quality in Oslo",
10401 "tree": "air-quality"
10402 },
10403 {
10404 "expect": {
10405 "inputs": {
10406 "place": "Delhi"
10407 },
10408 "output": {
10409 "aqi": 87,
10410 "band": "very poor",
10411 "country": "India",
10412 "place": "Delhi",
10413 "pm10": 118.3,
10414 "pm2_5": 61.4
10415 },
10416 "say": "Delhi, India: air quality very poor (European AQI 87), PM2.5 61.4 µg/m³, PM10 118.3 µg/m³.",
10417 "status": "done",
10418 "trace": {
10419 "gaps": [],
10420 "steps": [
10421 {
10422 "depth": 0,
10423 "kind": "intent",
10424 "next": "geocode",
10425 "node": "intent",
10426 "observed": {
10427 "cleaned": "how is the air in Delhi",
10428 "inputs": {
10429 "place": "Delhi"
10430 },
10431 "steps": [],
10432 "trigger": "how is the air in {place}"
10433 },
10434 "summary": "'how is the air in Delhi'; air-quality via 'how is the air in {place}' (13 literal chars)"
10435 },
10436 {
10437 "depth": 0,
10438 "kind": "observe",
10439 "next": "found",
10440 "node": "geocode",
10441 "observed": {
10442 "country": "India",
10443 "hit": {
10444 "country": "India",
10445 "latitude": 28.65,
10446 "longitude": 77.23,
10447 "name": "Delhi"
10448 },
10449 "lat": 28.65,
10450 "lon": 77.23,
10451 "name": "Delhi"
10452 },
10453 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Delhi&count=1&language=en&format=json -> 200; hit={'name': 'Delhi', 'country': 'India', 'latitude': 28.65, 'longitude': 77.23}, lat=28.65, lon=77.23, name=Delhi, country=India"
10454 },
10455 {
10456 "chosen": "exists True",
10457 "depth": 0,
10458 "kind": "decide",
10459 "next": "air",
10460 "node": "found",
10461 "observed": {
10462 "country": "India",
10463 "latitude": 28.65,
10464 "longitude": 77.23,
10465 "name": "Delhi"
10466 },
10467 "summary": "state.hit = {'name': 'Delhi', 'country': 'India', 'latitude': 28.65, 'longitude': 77.23}"
10468 },
10469 {
10470 "depth": 0,
10471 "kind": "observe",
10472 "next": "band",
10473 "node": "air",
10474 "observed": {
10475 "aqi": 87,
10476 "pm10": 118.3,
10477 "pm25": 61.4
10478 },
10479 "summary": "GET https://air-quality-api.open-meteo.com/v1/air-quality?latitude=28.65&longitude=77.23&current=european_aqi,pm2_5,pm10 -> 200; aqi=87, pm25=61.4, pm10=118.3"
10480 },
10481 {
10482 "chosen": "lte 100",
10483 "depth": 0,
10484 "kind": "decide",
10485 "next": "very-poor",
10486 "node": "band",
10487 "observed": 87,
10488 "summary": "state.aqi = 87"
10489 },
10490 {
10491 "depth": 0,
10492 "kind": "set",
10493 "next": "reply",
10494 "node": "very-poor",
10495 "observed": {
10496 "band": "very poor"
10497 },
10498 "summary": "band=very poor"
10499 },
10500 {
10501 "depth": 0,
10502 "kind": "done",
10503 "next": null,
10504 "node": "reply",
10505 "summary": "Delhi, India: air quality very poor (European AQI 87), PM2.5 61.4 µg/m³, PM10 118.3 µg/m³."
10506 }
10507 ],
10508 "tree": "air-quality"
10509 },
10510 "tree": "air-quality"
10511 },
10512 "inputs": {
10513 "place": "Delhi"
10514 },
10515 "intent": "how is the air in Delhi",
10516 "tree": "air-quality"
10517 },
10518 {
10519 "expect": {
10520 "inputs": {
10521 "package": "@types/node"
10522 },
10523 "output": {
10524 "description": "TypeScript definitions for node",
10525 "package": "@types/node",
10526 "url": "https://www.npmjs.com/package/@types/node",
10527 "version": "26.6.5"
10528 },
10529 "say": "@types/node 26.6.5 on npm: TypeScript definitions for node https://www.npmjs.com/package/@types/node",
10530 "status": "done",
10531 "trace": {
10532 "gaps": [],
10533 "steps": [
10534 {
10535 "depth": 0,
10536 "kind": "intent",
10537 "next": "fetch",
10538 "node": "intent",
10539 "observed": {
10540 "cleaned": "latest version of @types/node on npm",
10541 "inputs": {
10542 "package": "@types/node"
10543 },
10544 "steps": [],
10545 "trigger": "[what's the] latest version of {package:word} on npm"
10546 },
10547 "summary": "'latest version of @types/node on npm'; npm-package via \"[what's the] latest version of {package:word} on npm\" (29 literal chars)"
10548 },
10549 {
10550 "depth": 0,
10551 "kind": "observe",
10552 "next": "status",
10553 "node": "fetch",
10554 "observed": {
10555 "about": "TypeScript definitions for node",
10556 "version": "26.6.5"
10557 },
10558 "summary": "GET https://registry.npmjs.org/%40types%2Fnode/latest -> 200; version=26.6.5, about=TypeScript definitions for node"
10559 },
10560 {
10561 "chosen": "eq 200",
10562 "depth": 0,
10563 "kind": "decide",
10564 "next": "reply",
10565 "node": "status",
10566 "observed": 200,
10567 "summary": "state.resp.status = 200"
10568 },
10569 {
10570 "depth": 0,
10571 "kind": "done",
10572 "next": null,
10573 "node": "reply",
10574 "summary": "@types/node 26.6.5 on npm: TypeScript definitions for node https://www.npmjs.com/package/@types/node"
10575 }
10576 ],
10577 "tree": "npm-package"
10578 },
10579 "tree": "npm-package"
10580 },
10581 "inputs": {
10582 "package": "@types/node"
10583 },
10584 "intent": "latest version of @types/node on npm",
10585 "tree": "npm-package"
10586 },
10587 {
10588 "expect": {
10589 "inputs": {
10590 "package": "nope-not-a-pkg-zz"
10591 },
10592 "reason": "no-package",
10593 "say": "npm has no package called nope-not-a-pkg-zz.",
10594 "status": "failed",
10595 "trace": {
10596 "gaps": [],
10597 "steps": [
10598 {
10599 "depth": 0,
10600 "kind": "intent",
10601 "next": "fetch",
10602 "node": "intent",
10603 "observed": {
10604 "cleaned": "npm package nope-not-a-pkg-zz",
10605 "inputs": {
10606 "package": "nope-not-a-pkg-zz"
10607 },
10608 "steps": [],
10609 "trigger": "npm (package|version) {package:word}"
10610 },
10611 "summary": "'npm package nope-not-a-pkg-zz'; npm-package via 'npm (package|version) {package:word}' (17 literal chars)"
10612 },
10613 {
10614 "depth": 0,
10615 "kind": "observe",
10616 "next": "status",
10617 "node": "fetch",
10618 "observed": {
10619 "about": null,
10620 "version": null
10621 },
10622 "summary": "GET https://registry.npmjs.org/nope-not-a-pkg-zz/latest -> 404; version=None, about=None"
10623 },
10624 {
10625 "chosen": "eq 404",
10626 "depth": 0,
10627 "kind": "decide",
10628 "next": "no-package",
10629 "node": "status",
10630 "observed": 404,
10631 "summary": "state.resp.status = 404"
10632 },
10633 {
10634 "depth": 0,
10635 "kind": "fail",
10636 "next": null,
10637 "node": "no-package",
10638 "summary": "no-package: npm has no package called nope-not-a-pkg-zz."
10639 }
10640 ],
10641 "tree": "npm-package"
10642 },
10643 "tree": "npm-package"
10644 },
10645 "inputs": {
10646 "package": "nope-not-a-pkg-zz"
10647 },
10648 "intent": "npm package nope-not-a-pkg-zz",
10649 "tree": "npm-package"
10650 },
10651 {
10652 "expect": {
10653 "inputs": {
10654 "package": "requests"
10655 },
10656 "output": {
10657 "package": "requests",
10658 "summary": "Python HTTP for Humans.",
10659 "url": "https://pypi.org/project/requests/",
10660 "version": "2.34.2"
10661 },
10662 "say": "requests 2.34.2 on PyPI: Python HTTP for Humans. https://pypi.org/project/requests/",
10663 "status": "done",
10664 "trace": {
10665 "gaps": [],
10666 "steps": [
10667 {
10668 "depth": 0,
10669 "kind": "intent",
10670 "next": "fetch",
10671 "node": "intent",
10672 "observed": {
10673 "cleaned": "latest requests on pypi",
10674 "inputs": {
10675 "package": "requests"
10676 },
10677 "steps": [],
10678 "trigger": "latest {package:word} on pypi"
10679 },
10680 "summary": "'latest requests on pypi'; pypi-package via 'latest {package:word} on pypi' (12 literal chars)"
10681 },
10682 {
10683 "depth": 0,
10684 "kind": "observe",
10685 "next": "status",
10686 "node": "fetch",
10687 "observed": {
10688 "about": "Python HTTP for Humans.",
10689 "name": "requests",
10690 "url": "https://pypi.org/project/requests/",
10691 "version": "2.34.2"
10692 },
10693 "summary": "GET https://pypi.org/pypi/requests/json -> 200; name=requests, version=2.34.2, about=Python HTTP for Humans., url=https://pypi.org/project/requests/"
10694 },
10695 {
10696 "chosen": "eq 200",
10697 "depth": 0,
10698 "kind": "decide",
10699 "next": "reply",
10700 "node": "status",
10701 "observed": 200,
10702 "summary": "state.resp.status = 200"
10703 },
10704 {
10705 "depth": 0,
10706 "kind": "done",
10707 "next": null,
10708 "node": "reply",
10709 "summary": "requests 2.34.2 on PyPI: Python HTTP for Humans. https://pypi.org/project/requests/"
10710 }
10711 ],
10712 "tree": "pypi-package"
10713 },
10714 "tree": "pypi-package"
10715 },
10716 "inputs": {
10717 "package": "requests"
10718 },
10719 "intent": "latest requests on pypi",
10720 "tree": "pypi-package"
10721 },
10722 {
10723 "expect": {
10724 "inputs": {
10725 "package": "nope-not-a-pkg-zz"
10726 },
10727 "reason": "no-package",
10728 "say": "PyPI has no project called nope-not-a-pkg-zz.",
10729 "status": "failed",
10730 "trace": {
10731 "gaps": [],
10732 "steps": [
10733 {
10734 "depth": 0,
10735 "kind": "intent",
10736 "next": "fetch",
10737 "node": "intent",
10738 "observed": {
10739 "cleaned": "pip version nope-not-a-pkg-zz",
10740 "inputs": {
10741 "package": "nope-not-a-pkg-zz"
10742 },
10743 "steps": [],
10744 "trigger": "(pypi|pip) (package|version) {package:word}"
10745 },
10746 "summary": "'pip version nope-not-a-pkg-zz'; pypi-package via '(pypi|pip) (package|version) {package:word}' (21 literal chars)"
10747 },
10748 {
10749 "depth": 0,
10750 "kind": "observe",
10751 "next": "status",
10752 "node": "fetch",
10753 "observed": {
10754 "about": null,
10755 "name": null,
10756 "url": null,
10757 "version": null
10758 },
10759 "summary": "GET https://pypi.org/pypi/nope-not-a-pkg-zz/json -> 404; name=None, version=None, about=None, url=None"
10760 },
10761 {
10762 "chosen": "eq 404",
10763 "depth": 0,
10764 "kind": "decide",
10765 "next": "no-package",
10766 "node": "status",
10767 "observed": 404,
10768 "summary": "state.resp.status = 404"
10769 },
10770 {
10771 "depth": 0,
10772 "kind": "fail",
10773 "next": null,
10774 "node": "no-package",
10775 "summary": "no-package: PyPI has no project called nope-not-a-pkg-zz."
10776 }
10777 ],
10778 "tree": "pypi-package"
10779 },
10780 "tree": "pypi-package"
10781 },
10782 "inputs": {
10783 "package": "nope-not-a-pkg-zz"
10784 },
10785 "intent": "pip version nope-not-a-pkg-zz",
10786 "tree": "pypi-package"
10787 },
10788 {
10789 "expect": {
10790 "inputs": {},
10791 "output": {
10792 "count": 19,
10793 "latest": {
10794 "magnitude": 5.1,
10795 "place": "7 km ENE of Pangyan, Philippines",
10796 "tsunami": 0,
10797 "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000ttkk"
10798 }
10799 },
10800 "say": "19 earthquake(s) of magnitude 4.5+ in the past day. The latest: M5.1, 7 km ENE of Pangyan, Philippines. https://earthquake.usgs.gov/earthquakes/eventpage/us7000ttkk",
10801 "status": "done",
10802 "trace": {
10803 "gaps": [],
10804 "steps": [
10805 {
10806 "depth": 0,
10807 "kind": "intent",
10808 "next": "fetch",
10809 "node": "intent",
10810 "observed": {
10811 "cleaned": "any earthquakes today",
10812 "inputs": {},
10813 "steps": [],
10814 "trigger": "[any] [recent] earthquakes [today]"
10815 },
10816 "summary": "'any earthquakes today'; earthquakes via '[any] [recent] earthquakes [today]' (25 literal chars)"
10817 },
10818 {
10819 "depth": 0,
10820 "kind": "observe",
10821 "next": "status",
10822 "node": "fetch",
10823 "observed": {
10824 "count": 19,
10825 "mag": 5.1,
10826 "place": "7 km ENE of Pangyan, Philippines",
10827 "tsunami": 0,
10828 "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us7000ttkk"
10829 },
10830 "summary": "GET https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson -> 200; count=19, mag=5.1, place=7 km ENE of Pangyan, Philippines, tsunami=0, url=https://earthquake.usgs.gov/earthquakes/eventpage/us7000ttkk"
10831 },
10832 {
10833 "chosen": "eq 200",
10834 "depth": 0,
10835 "kind": "decide",
10836 "next": "any",
10837 "node": "status",
10838 "observed": 200,
10839 "summary": "state.resp.status = 200"
10840 },
10841 {
10842 "chosen": "gt 0",
10843 "depth": 0,
10844 "kind": "decide",
10845 "next": "reply",
10846 "node": "any",
10847 "observed": 19,
10848 "summary": "state.count = 19"
10849 },
10850 {
10851 "depth": 0,
10852 "kind": "done",
10853 "next": null,
10854 "node": "reply",
10855 "summary": "19 earthquake(s) of magnitude 4.5+ in the past day. The latest: M5.1, 7 km ENE of Pangyan, Philippines. https://earthquake.usgs.gov/earthquakes/eventpage/us7000ttkk"
10856 }
10857 ],
10858 "tree": "earthquakes"
10859 },
10860 "tree": "earthquakes"
10861 },
10862 "inputs": {},
10863 "intent": "any earthquakes today",
10864 "tree": "earthquakes"
10865 },
10866 {
10867 "expect": {
10868 "inputs": {},
10869 "output": {
10870 "altitude_km": 422.43868994227,
10871 "latitude": -26.625662371377,
10872 "longitude": -60.102704384113,
10873 "map": "https://www.openstreetmap.org/?mlat=-26.625662371377&mlon=-60.102704384113&zoom=3",
10874 "speed_kmh": 27577.69858816,
10875 "visibility": "eclipsed"
10876 },
10877 "say": "The ISS is over -26.62, -60.10, 422 km up, moving at 27577 km/h, in the Earth's shadow.",
10878 "status": "done",
10879 "trace": {
10880 "gaps": [],
10881 "steps": [
10882 {
10883 "depth": 0,
10884 "kind": "intent",
10885 "next": "fetch",
10886 "node": "intent",
10887 "observed": {
10888 "cleaned": "where is the iss",
10889 "inputs": {},
10890 "steps": [],
10891 "trigger": "where is the (iss|space station) [now]"
10892 },
10893 "summary": "'where is the iss'; iss via 'where is the (iss|space station) [now]' (28 literal chars)"
10894 },
10895 {
10896 "depth": 0,
10897 "kind": "observe",
10898 "next": "status",
10899 "node": "fetch",
10900 "observed": {
10901 "alt": 422.43868994227,
10902 "lat": -26.625662371377,
10903 "lat2": "-26.62",
10904 "light": "eclipsed",
10905 "lon": -60.102704384113,
10906 "lon2": "-60.10",
10907 "speed": 27577.69858816
10908 },
10909 "summary": "GET https://api.wheretheiss.at/v1/satellites/25544 -> 200; lat=-26.625662371377, lon=-60.102704384113, alt=422.43868994227, speed=27577.69858816, light=eclipsed, lat2=-26.62, lon2=-60.10"
10910 },
10911 {
10912 "chosen": "eq 200",
10913 "depth": 0,
10914 "kind": "decide",
10915 "next": "have-position",
10916 "node": "status",
10917 "observed": 200,
10918 "summary": "state.resp.status = 200"
10919 },
10920 {
10921 "chosen": "type 'number'",
10922 "depth": 0,
10923 "kind": "decide",
10924 "next": "light",
10925 "node": "have-position",
10926 "observed": -26.625662371377,
10927 "summary": "state.lat = -26.625662371377"
10928 },
10929 {
10930 "chosen": "eq 'eclipsed'",
10931 "depth": 0,
10932 "kind": "decide",
10933 "next": "in-shadow",
10934 "node": "light",
10935 "observed": "eclipsed",
10936 "summary": "state.light = eclipsed"
10937 },
10938 {
10939 "depth": 0,
10940 "kind": "set",
10941 "next": "reply",
10942 "node": "in-shadow",
10943 "observed": {
10944 "where": "in the Earth's shadow"
10945 },
10946 "summary": "where=in the Earth's shadow"
10947 },
10948 {
10949 "depth": 0,
10950 "kind": "done",
10951 "next": null,
10952 "node": "reply",
10953 "summary": "The ISS is over -26.62, -60.10, 422 km up, moving at 27577 km/h, in the Earth's shadow."
10954 }
10955 ],
10956 "tree": "iss"
10957 },
10958 "tree": "iss"
10959 },
10960 "inputs": {},
10961 "intent": "where is the iss",
10962 "tree": "iss"
10963 },
10964 {
10965 "expect": {
10966 "inputs": {
10967 "query": "sqlite"
10968 },
10969 "output": {
10970 "query": "sqlite",
10971 "stories": [
10972 {
10973 "by": "phiresky",
10974 "discussion": "https://news.ycombinator.com/item?id=27016630",
10975 "points": 1812,
10976 "title": "Hosting SQLite databases on GitHub Pages or any static file hoster",
10977 "url": "https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/"
10978 },
10979 {
10980 "by": "dpeck",
10981 "discussion": "https://news.ycombinator.com/item?id=31318708",
10982 "points": 1353,
10983 "title": "I'm all-in on server-side SQLite",
10984 "url": "https://fly.io/blog/all-in-on-sqlite-litestream/"
10985 },
10986 {
10987 "by": "ropbear",
10988 "discussion": "https://news.ycombinator.com/item?id=49272832",
10989 "points": 1223,
10990 "title": "Tracking down the 16-year-old WAL-reset SQLite bug",
10991 "url": "https://tailscale.com/blog/sqlite-wal-reset-bug"
10992 }
10993 ],
10994 "total": 4632
10995 },
10996 "say": "4632 Hacker News stories match sqlite. Top: Hosting SQLite databases on GitHub Pages or any static file hoster https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/",
10997 "status": "done",
10998 "trace": {
10999 "gaps": [],
11000 "steps": [
11001 {
11002 "depth": 0,
11003 "kind": "intent",
11004 "next": "search",
11005 "node": "intent",
11006 "observed": {
11007 "cleaned": "search hacker news for sqlite",
11008 "inputs": {
11009 "query": "sqlite"
11010 },
11011 "steps": [],
11012 "trigger": "search hacker news for {query}"
11013 },
11014 "summary": "'search hacker news for sqlite'; hn-search via 'search hacker news for {query}' (19 literal chars)"
11015 },
11016 {
11017 "depth": 0,
11018 "kind": "observe",
11019 "next": "status",
11020 "node": "search",
11021 "observed": {
11022 "first": "Hosting SQLite databases on GitHub Pages or any static file hoster",
11023 "first_url": "https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/",
11024 "hits": [
11025 {
11026 "author": "phiresky",
11027 "objectID": "27016630",
11028 "points": 1812,
11029 "title": "Hosting SQLite databases on GitHub Pages or any static file hoster",
11030 "url": "https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/"
11031 },
11032 {
11033 "author": "dpeck",
11034 "objectID": "31318708",
11035 "points": 1353,
11036 "title": "I'm all-in on server-side SQLite",
11037 "url": "https://fly.io/blog/all-in-on-sqlite-litestream/"
11038 },
11039 {
11040 "author": "ropbear",
11041 "objectID": "49272832",
11042 "points": 1223,
11043 "title": "Tracking down the 16-year-old WAL-reset SQLite bug",
11044 "url": "https://tailscale.com/blog/sqlite-wal-reset-bug"
11045 }
11046 ],
11047 "total": 4632
11048 },
11049 "summary": "GET https://hn.algolia.com/api/v1/search?query=sqlite&tags=story&hitsPerPage=3 -> 200; total=4632, first=Hosting SQLite databases on GitHub Pages or any static file hoster, first_url=https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/, hits=[{'title': 'Hosting SQLite databases on GitHub Pages or any static file hoster'…"
11050 },
11051 {
11052 "chosen": "eq 200",
11053 "depth": 0,
11054 "kind": "decide",
11055 "next": "any",
11056 "node": "status",
11057 "observed": 200,
11058 "summary": "state.resp.status = 200"
11059 },
11060 {
11061 "chosen": "else",
11062 "depth": 0,
11063 "kind": "decide",
11064 "next": "each",
11065 "node": "any",
11066 "observed": [
11067 {
11068 "author": "phiresky",
11069 "objectID": "27016630",
11070 "points": 1812,
11071 "title": "Hosting SQLite databases on GitHub Pages or any static file hoster",
11072 "url": "https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/"
11073 },
11074 {
11075 "author": "dpeck",
11076 "objectID": "31318708",
11077 "points": 1353,
11078 "title": "I'm all-in on server-side SQLite",
11079 "url": "https://fly.io/blog/all-in-on-sqlite-litestream/"
11080 },
11081 {
11082 "author": "ropbear",
11083 "objectID": "49272832",
11084 "points": 1223,
11085 "title": "Tracking down the 16-year-old WAL-reset SQLite bug",
11086 "url": "https://tailscale.com/blog/sqlite-wal-reset-bug"
11087 }
11088 ],
11089 "summary": "state.hits = [{'title': 'Hosting SQLite databases on GitHub Pages or any static file hoster'…"
11090 },
11091 {
11092 "depth": 1,
11093 "kind": "done",
11094 "next": null,
11095 "node": "one",
11096 "summary": "done"
11097 },
11098 {
11099 "depth": 1,
11100 "kind": "done",
11101 "next": null,
11102 "node": "one",
11103 "summary": "done"
11104 },
11105 {
11106 "depth": 1,
11107 "kind": "done",
11108 "next": null,
11109 "node": "one",
11110 "summary": "done"
11111 },
11112 {
11113 "depth": 0,
11114 "kind": "each",
11115 "next": "reply",
11116 "node": "each",
11117 "summary": "3 item(s)"
11118 },
11119 {
11120 "depth": 0,
11121 "kind": "done",
11122 "next": null,
11123 "node": "reply",
11124 "summary": "4632 Hacker News stories match sqlite. Top: Hosting SQLite databases on GitHub Pages or any static file hoster https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/"
11125 }
11126 ],
11127 "tree": "hn-search"
11128 },
11129 "tree": "hn-search"
11130 },
11131 "inputs": {
11132 "query": "sqlite"
11133 },
11134 "intent": "search hacker news for sqlite",
11135 "tree": "hn-search"
11136 },
11137 {
11138 "expect": {
11139 "inputs": {
11140 "query": "qqqzzzxxyyy"
11141 },
11142 "output": {
11143 "query": "qqqzzzxxyyy",
11144 "stories": [],
11145 "total": 0
11146 },
11147 "say": "No Hacker News stories match qqqzzzxxyyy.",
11148 "status": "done",
11149 "trace": {
11150 "gaps": [],
11151 "steps": [
11152 {
11153 "depth": 0,
11154 "kind": "intent",
11155 "next": "search",
11156 "node": "intent",
11157 "observed": {
11158 "cleaned": "search hacker news for qqqzzzxxyyy",
11159 "inputs": {
11160 "query": "qqqzzzxxyyy"
11161 },
11162 "steps": [],
11163 "trigger": "search hacker news for {query}"
11164 },
11165 "summary": "'search hacker news for qqqzzzxxyyy'; hn-search via 'search hacker news for {query}' (19 literal chars)"
11166 },
11167 {
11168 "depth": 0,
11169 "kind": "observe",
11170 "next": "status",
11171 "node": "search",
11172 "observed": {
11173 "first": null,
11174 "first_url": null,
11175 "hits": [],
11176 "total": 0
11177 },
11178 "summary": "GET https://hn.algolia.com/api/v1/search?query=qqqzzzxxyyy&tags=story&hitsPerPage=3 -> 200; total=0, first=None, first_url=None, hits=[]"
11179 },
11180 {
11181 "chosen": "eq 200",
11182 "depth": 0,
11183 "kind": "decide",
11184 "next": "any",
11185 "node": "status",
11186 "observed": 200,
11187 "summary": "state.resp.status = 200"
11188 },
11189 {
11190 "chosen": "empty True",
11191 "depth": 0,
11192 "kind": "decide",
11193 "next": "nothing",
11194 "node": "any",
11195 "observed": [],
11196 "summary": "state.hits = []"
11197 },
11198 {
11199 "depth": 0,
11200 "kind": "done",
11201 "next": null,
11202 "node": "nothing",
11203 "summary": "No Hacker News stories match qqqzzzxxyyy."
11204 }
11205 ],
11206 "tree": "hn-search"
11207 },
11208 "tree": "hn-search"
11209 },
11210 "inputs": {
11211 "query": "qqqzzzxxyyy"
11212 },
11213 "intent": "search hacker news for qqqzzzxxyyy",
11214 "tree": "hn-search"
11215 },
11216 {
11217 "expect": {
11218 "inputs": {
11219 "title": "The Left Hand of Darkness"
11220 },
11221 "output": {
11222 "author": "Ursula K. Le Guin",
11223 "first_published": 1969,
11224 "matches": 112,
11225 "title": "The Left Hand of Darkness",
11226 "url": "https://openlibrary.org/works/OL59800W"
11227 },
11228 "say": "The Left Hand of Darkness, by Ursula K. Le Guin (1969). https://openlibrary.org/works/OL59800W",
11229 "status": "done",
11230 "trace": {
11231 "gaps": [],
11232 "steps": [
11233 {
11234 "depth": 0,
11235 "kind": "intent",
11236 "next": "search",
11237 "node": "intent",
11238 "observed": {
11239 "cleaned": "who wrote The Left Hand of Darkness",
11240 "inputs": {
11241 "title": "The Left Hand of Darkness"
11242 },
11243 "steps": [],
11244 "trigger": "who wrote {title}"
11245 },
11246 "summary": "'who wrote The Left Hand of Darkness'; books via 'who wrote {title}' (8 literal chars)"
11247 },
11248 {
11249 "depth": 0,
11250 "kind": "observe",
11251 "next": "status",
11252 "node": "search",
11253 "observed": {
11254 "author": "Ursula K. Le Guin",
11255 "book": "The Left Hand of Darkness",
11256 "found": 112,
11257 "key": "/works/OL59800W",
11258 "year": 1969
11259 },
11260 "summary": "GET https://openlibrary.org/search.json?q=The%20Left%20Hand%20of%20Darkness&limit=1&fields=title,author_name,first_publish_year,key -> 200; found=112, book=The Left Hand of Darkness, author=Ursula K. Le Guin, year=1969, key=/works/OL59800W"
11261 },
11262 {
11263 "chosen": "eq 200",
11264 "depth": 0,
11265 "kind": "decide",
11266 "next": "any",
11267 "node": "status",
11268 "observed": 200,
11269 "summary": "state.resp.status = 200"
11270 },
11271 {
11272 "chosen": "exists True",
11273 "depth": 0,
11274 "kind": "decide",
11275 "next": "reply",
11276 "node": "any",
11277 "observed": "The Left Hand of Darkness",
11278 "summary": "state.book = The Left Hand of Darkness"
11279 },
11280 {
11281 "depth": 0,
11282 "kind": "done",
11283 "next": null,
11284 "node": "reply",
11285 "summary": "The Left Hand of Darkness, by Ursula K. Le Guin (1969). https://openlibrary.org/works/OL59800W"
11286 }
11287 ],
11288 "tree": "books"
11289 },
11290 "tree": "books"
11291 },
11292 "inputs": {
11293 "title": "The Left Hand of Darkness"
11294 },
11295 "intent": "who wrote The Left Hand of Darkness",
11296 "tree": "books"
11297 },
11298 {
11299 "expect": {
11300 "inputs": {
11301 "title": "qqqzzzxxyyy"
11302 },
11303 "reason": "no-book",
11304 "say": "Open Library has nothing matching qqqzzzxxyyy.",
11305 "status": "failed",
11306 "trace": {
11307 "gaps": [],
11308 "steps": [
11309 {
11310 "depth": 0,
11311 "kind": "intent",
11312 "next": "search",
11313 "node": "intent",
11314 "observed": {
11315 "cleaned": "find the book qqqzzzxxyyy",
11316 "inputs": {
11317 "title": "qqqzzzxxyyy"
11318 },
11319 "steps": [],
11320 "trigger": "find the book {title}"
11321 },
11322 "summary": "'find the book qqqzzzxxyyy'; books via 'find the book {title}' (11 literal chars)"
11323 },
11324 {
11325 "depth": 0,
11326 "kind": "observe",
11327 "next": "status",
11328 "node": "search",
11329 "observed": {
11330 "author": null,
11331 "book": null,
11332 "found": 0,
11333 "key": null,
11334 "year": null
11335 },
11336 "summary": "GET https://openlibrary.org/search.json?q=qqqzzzxxyyy&limit=1&fields=title,author_name,first_publish_year,key -> 200; found=0, book=None, author=None, year=None, key=None"
11337 },
11338 {
11339 "chosen": "eq 200",
11340 "depth": 0,
11341 "kind": "decide",
11342 "next": "any",
11343 "node": "status",
11344 "observed": 200,
11345 "summary": "state.resp.status = 200"
11346 },
11347 {
11348 "chosen": "else",
11349 "depth": 0,
11350 "kind": "decide",
11351 "next": "no-book",
11352 "node": "any",
11353 "summary": "state.book = <missing>"
11354 },
11355 {
11356 "depth": 0,
11357 "kind": "fail",
11358 "next": null,
11359 "node": "no-book",
11360 "summary": "no-book: Open Library has nothing matching qqqzzzxxyyy."
11361 }
11362 ],
11363 "tree": "books"
11364 },
11365 "tree": "books"
11366 },
11367 "inputs": {
11368 "title": "qqqzzzxxyyy"
11369 },
11370 "intent": "find the book qqqzzzxxyyy",
11371 "tree": "books"
11372 },
11373 {
11374 "expect": {
11375 "inputs": {
11376 "country": "no"
11377 },
11378 "output": {
11379 "after": {
11380 "date": "2026-12-26",
11381 "name": "St. Stephen's Day"
11382 },
11383 "country": "NO",
11384 "date": "2026-12-25",
11385 "local_name": "Første juledag",
11386 "name": "Christmas Day"
11387 },
11388 "say": "The next public holiday in NO is Christmas Day (Første juledag) on 2026-12-25.",
11389 "status": "done",
11390 "trace": {
11391 "gaps": [],
11392 "steps": [
11393 {
11394 "depth": 0,
11395 "kind": "intent",
11396 "next": "fetch",
11397 "node": "intent",
11398 "observed": {
11399 "cleaned": "next holiday in no",
11400 "inputs": {
11401 "country": "no"
11402 },
11403 "steps": [],
11404 "trigger": "[when is the] next [public|bank] holiday in {country:word}"
11405 },
11406 "summary": "'next holiday in no'; holidays via '[when is the] next [public|bank] holiday in {country:word}' (32 literal chars)"
11407 },
11408 {
11409 "depth": 0,
11410 "kind": "observe",
11411 "next": "status",
11412 "node": "fetch",
11413 "observed": {
11414 "date": "2026-12-25",
11415 "local": "Første juledag",
11416 "name": "Christmas Day",
11417 "then": "St. Stephen's Day",
11418 "then_date": "2026-12-26"
11419 },
11420 "summary": "GET https://date.nager.at/api/v3/NextPublicHolidays/NO -> 200; date=2026-12-25, name=Christmas Day, local=Første juledag, then=St. Stephen's Day, then_date=2026-12-26"
11421 },
11422 {
11423 "chosen": "eq 200",
11424 "depth": 0,
11425 "kind": "decide",
11426 "next": "any",
11427 "node": "status",
11428 "observed": 200,
11429 "summary": "state.resp.status = 200"
11430 },
11431 {
11432 "chosen": "exists True",
11433 "depth": 0,
11434 "kind": "decide",
11435 "next": "reply",
11436 "node": "any",
11437 "observed": "2026-12-25",
11438 "summary": "state.date = 2026-12-25"
11439 },
11440 {
11441 "depth": 0,
11442 "kind": "done",
11443 "next": null,
11444 "node": "reply",
11445 "summary": "The next public holiday in NO is Christmas Day (Første juledag) on 2026-12-25."
11446 }
11447 ],
11448 "tree": "holidays"
11449 },
11450 "tree": "holidays"
11451 },
11452 "inputs": {
11453 "country": "no"
11454 },
11455 "intent": "next holiday in no",
11456 "tree": "holidays"
11457 },
11458 {
11459 "expect": {
11460 "inputs": {
11461 "country": "XX"
11462 },
11463 "reason": "unknown-country",
11464 "say": "Nager.Date knows no country XX (it answered 500); use a two-letter code such as NO, US or DE.",
11465 "status": "failed",
11466 "trace": {
11467 "gaps": [],
11468 "steps": [
11469 {
11470 "depth": 0,
11471 "kind": "intent",
11472 "next": "fetch",
11473 "node": "intent",
11474 "observed": {
11475 "cleaned": "next holiday in XX",
11476 "inputs": {
11477 "country": "XX"
11478 },
11479 "steps": [],
11480 "trigger": "[when is the] next [public|bank] holiday in {country:word}"
11481 },
11482 "summary": "'next holiday in XX'; holidays via '[when is the] next [public|bank] holiday in {country:word}' (32 literal chars)"
11483 },
11484 {
11485 "depth": 0,
11486 "kind": "observe",
11487 "next": "status",
11488 "node": "fetch",
11489 "observed": {
11490 "date": null,
11491 "local": null,
11492 "name": null,
11493 "then": null,
11494 "then_date": null
11495 },
11496 "summary": "GET https://date.nager.at/api/v3/NextPublicHolidays/XX -> 500; date=None, name=None, local=None, then=None, then_date=None"
11497 },
11498 {
11499 "chosen": "else",
11500 "depth": 0,
11501 "kind": "decide",
11502 "next": "unknown-country",
11503 "node": "status",
11504 "observed": 500,
11505 "summary": "state.resp.status = 500"
11506 },
11507 {
11508 "depth": 0,
11509 "kind": "fail",
11510 "next": null,
11511 "node": "unknown-country",
11512 "summary": "unknown-country: Nager.Date knows no country XX (it answered 500); use a two-letter code such as NO, US or DE."
11513 }
11514 ],
11515 "tree": "holidays"
11516 },
11517 "tree": "holidays"
11518 },
11519 "inputs": {
11520 "country": "XX"
11521 },
11522 "intent": "next holiday in XX",
11523 "tree": "holidays"
11524 },
11525 {
11526 "expect": {
11527 "inputs": {},
11528 "output": {
11529 "joke": "I am so good at sleeping I can do it with my eyes closed!",
11530 "url": "https://icanhazdadjoke.com/j/oOKBsPZ0wc"
11531 },
11532 "say": "I am so good at sleeping I can do it with my eyes closed!",
11533 "status": "done",
11534 "trace": {
11535 "gaps": [],
11536 "steps": [
11537 {
11538 "depth": 0,
11539 "kind": "intent",
11540 "next": "fetch",
11541 "node": "intent",
11542 "observed": {
11543 "cleaned": "tell me a joke",
11544 "inputs": {},
11545 "steps": [],
11546 "trigger": "tell me a [dad] joke"
11547 },
11548 "summary": "'tell me a joke'; dad-joke via 'tell me a [dad] joke' (14 literal chars)"
11549 },
11550 {
11551 "depth": 0,
11552 "kind": "observe",
11553 "next": "status",
11554 "node": "fetch",
11555 "observed": {
11556 "id": "oOKBsPZ0wc",
11557 "joke": "I am so good at sleeping I can do it with my eyes closed!"
11558 },
11559 "summary": "GET https://icanhazdadjoke.com/ -> 200; joke=I am so good at sleeping I can do it with my eyes closed!, id=oOKBsPZ0wc"
11560 },
11561 {
11562 "chosen": "eq 200",
11563 "depth": 0,
11564 "kind": "decide",
11565 "next": "has-joke",
11566 "node": "status",
11567 "observed": 200,
11568 "summary": "state.resp.status = 200"
11569 },
11570 {
11571 "chosen": "exists True",
11572 "depth": 0,
11573 "kind": "decide",
11574 "next": "reply",
11575 "node": "has-joke",
11576 "observed": "I am so good at sleeping I can do it with my eyes closed!",
11577 "summary": "state.joke = I am so good at sleeping I can do it with my eyes closed!"
11578 },
11579 {
11580 "depth": 0,
11581 "kind": "done",
11582 "next": null,
11583 "node": "reply",
11584 "summary": "I am so good at sleeping I can do it with my eyes closed!"
11585 }
11586 ],
11587 "tree": "dad-joke"
11588 },
11589 "tree": "dad-joke"
11590 },
11591 "inputs": {},
11592 "intent": "tell me a joke",
11593 "tree": "dad-joke"
11594 },
11595 {
11596 "expect": {
11597 "inputs": {
11598 "amount": "100",
11599 "from": "usd",
11600 "to": "eur"
11601 },
11602 "output": {
11603 "amount": 100.0,
11604 "date": "2026-10-27",
11605 "from": "USD",
11606 "to": "EUR",
11607 "value": 89.69
11608 },
11609 "say": "100 USD is 89.69 EUR at the ECB rate of 2026-10-27.",
11610 "status": "done",
11611 "trace": {
11612 "gaps": [],
11613 "steps": [
11614 {
11615 "depth": 0,
11616 "kind": "intent",
11617 "next": "fetch",
11618 "node": "intent",
11619 "observed": {
11620 "cleaned": "convert 100 usd to eur",
11621 "inputs": {
11622 "amount": 100,
11623 "from": "usd",
11624 "to": "eur"
11625 },
11626 "steps": [],
11627 "trigger": "convert {amount:number} {from:word} (to|into|in) {to:word}"
11628 },
11629 "summary": "'convert 100 usd to eur'; exchange-rate via 'convert {amount:number} {from:word} (to|into|in) {to:word}' (15 literal chars)"
11630 },
11631 {
11632 "depth": 0,
11633 "kind": "observe",
11634 "next": "status",
11635 "node": "fetch",
11636 "observed": {
11637 "day": "2026-10-27",
11638 "value": "89.69"
11639 },
11640 "summary": "GET https://api.frankfurter.dev/v1/latest?base=USD&symbols=EUR&amount=100 -> 200; value=89.69, day=2026-10-27"
11641 },
11642 {
11643 "chosen": "eq 200",
11644 "depth": 0,
11645 "kind": "decide",
11646 "next": "has-rate",
11647 "node": "status",
11648 "observed": 200,
11649 "summary": "state.resp.status = 200"
11650 },
11651 {
11652 "chosen": "exists True",
11653 "depth": 0,
11654 "kind": "decide",
11655 "next": "reply",
11656 "node": "has-rate",
11657 "observed": "89.69",
11658 "summary": "state.value = 89.69"
11659 },
11660 {
11661 "depth": 0,
11662 "kind": "done",
11663 "next": null,
11664 "node": "reply",
11665 "summary": "100 USD is 89.69 EUR at the ECB rate of 2026-10-27."
11666 }
11667 ],
11668 "tree": "exchange-rate"
11669 },
11670 "tree": "exchange-rate"
11671 },
11672 "inputs": {
11673 "amount": 100,
11674 "from": "usd",
11675 "to": "eur"
11676 },
11677 "intent": "convert 100 usd to eur",
11678 "tree": "exchange-rate"
11679 },
11680 {
11681 "expect": {
11682 "inputs": {
11683 "amount": "1000",
11684 "from": "EUR",
11685 "to": "JPY"
11686 },
11687 "output": {
11688 "amount": 1000.0,
11689 "date": "2026-10-27",
11690 "from": "EUR",
11691 "to": "JPY",
11692 "value": 175970.0
11693 },
11694 "say": "1000 EUR is 175970 JPY at the ECB rate of 2026-10-27.",
11695 "status": "done",
11696 "trace": {
11697 "gaps": [],
11698 "steps": [
11699 {
11700 "depth": 0,
11701 "kind": "intent",
11702 "next": "fetch",
11703 "node": "intent",
11704 "observed": {
11705 "cleaned": "convert 1,000 EUR into JPY",
11706 "inputs": {
11707 "amount": 1000,
11708 "from": "EUR",
11709 "to": "JPY"
11710 },
11711 "notes": [
11712 "amount: '1,000' read as 1000"
11713 ],
11714 "steps": [],
11715 "trigger": "convert {amount:number} {from:word} (to|into|in) {to:word}"
11716 },
11717 "summary": "'convert 1,000 EUR into JPY'; exchange-rate via 'convert {amount:number} {from:word} (to|into|in) {to:word}' (15 literal chars); amount: '1,000' read as 1000"
11718 },
11719 {
11720 "depth": 0,
11721 "kind": "observe",
11722 "next": "status",
11723 "node": "fetch",
11724 "observed": {
11725 "day": "2026-10-27",
11726 "value": "175970"
11727 },
11728 "summary": "GET https://api.frankfurter.dev/v1/latest?base=EUR&symbols=JPY&amount=1000 -> 200; value=175970, day=2026-10-27"
11729 },
11730 {
11731 "chosen": "eq 200",
11732 "depth": 0,
11733 "kind": "decide",
11734 "next": "has-rate",
11735 "node": "status",
11736 "observed": 200,
11737 "summary": "state.resp.status = 200"
11738 },
11739 {
11740 "chosen": "exists True",
11741 "depth": 0,
11742 "kind": "decide",
11743 "next": "reply",
11744 "node": "has-rate",
11745 "observed": "175970",
11746 "summary": "state.value = 175970"
11747 },
11748 {
11749 "depth": 0,
11750 "kind": "done",
11751 "next": null,
11752 "node": "reply",
11753 "summary": "1000 EUR is 175970 JPY at the ECB rate of 2026-10-27."
11754 }
11755 ],
11756 "tree": "exchange-rate"
11757 },
11758 "tree": "exchange-rate"
11759 },
11760 "inputs": {
11761 "amount": 1000,
11762 "from": "EUR",
11763 "to": "JPY"
11764 },
11765 "intent": "convert 1,000 EUR into JPY",
11766 "tree": "exchange-rate"
11767 },
11768 {
11769 "expect": {
11770 "inputs": {
11771 "amount": "1",
11772 "from": "usd",
11773 "to": "xyz"
11774 },
11775 "reason": "unknown-currency",
11776 "say": "The ECB publishes no rate between USD and XYZ; it covers about thirty currencies, by their three-letter codes.",
11777 "status": "failed",
11778 "trace": {
11779 "gaps": [],
11780 "steps": [
11781 {
11782 "depth": 0,
11783 "kind": "intent",
11784 "next": "fetch",
11785 "node": "intent",
11786 "observed": {
11787 "cleaned": "usd to xyz rate",
11788 "inputs": {
11789 "from": "usd",
11790 "to": "xyz"
11791 },
11792 "steps": [],
11793 "trigger": "{from:word} to {to:word} (rate|exchange rate)"
11794 },
11795 "summary": "'usd to xyz rate'; exchange-rate via '{from:word} to {to:word} (rate|exchange rate)' (18 literal chars)"
11796 },
11797 {
11798 "depth": 0,
11799 "kind": "observe",
11800 "next": "status",
11801 "node": "fetch",
11802 "observed": {
11803 "day": null,
11804 "value": null
11805 },
11806 "summary": "GET https://api.frankfurter.dev/v1/latest?base=USD&symbols=XYZ&amount=1 -> 404; value=None, day=None"
11807 },
11808 {
11809 "chosen": "eq 404",
11810 "depth": 0,
11811 "kind": "decide",
11812 "next": "unknown-currency",
11813 "node": "status",
11814 "observed": 404,
11815 "summary": "state.resp.status = 404"
11816 },
11817 {
11818 "depth": 0,
11819 "kind": "fail",
11820 "next": null,
11821 "node": "unknown-currency",
11822 "summary": "unknown-currency: The ECB publishes no rate between USD and XYZ; it covers about thirty currencies, by their three-letter codes."
11823 }
11824 ],
11825 "tree": "exchange-rate"
11826 },
11827 "tree": "exchange-rate"
11828 },
11829 "inputs": {
11830 "from": "usd",
11831 "to": "xyz"
11832 },
11833 "intent": "usd to xyz rate",
11834 "tree": "exchange-rate"
11835 },
11836 {
11837 "expect": {
11838 "inputs": {
11839 "amount": "1",
11840 "from": "usd",
11841 "to": "usd"
11842 },
11843 "reason": "same-currency",
11844 "say": "USD to USD is the same currency.",
11845 "status": "failed",
11846 "trace": {
11847 "gaps": [],
11848 "steps": [
11849 {
11850 "depth": 0,
11851 "kind": "intent",
11852 "next": "fetch",
11853 "node": "intent",
11854 "observed": {
11855 "cleaned": "exchange rate from usd to usd",
11856 "inputs": {
11857 "from": "usd",
11858 "to": "usd"
11859 },
11860 "steps": [],
11861 "trigger": "[the] exchange rate (from|for) {from:word} to {to:word}"
11862 },
11863 "summary": "'exchange rate from usd to usd'; exchange-rate via '[the] exchange rate (from|for) {from:word} to {to:word}' (24 literal chars)"
11864 },
11865 {
11866 "depth": 0,
11867 "kind": "observe",
11868 "next": "status",
11869 "node": "fetch",
11870 "observed": {
11871 "day": null,
11872 "value": null
11873 },
11874 "summary": "GET https://api.frankfurter.dev/v1/latest?base=USD&symbols=USD&amount=1 -> 422; value=None, day=None"
11875 },
11876 {
11877 "chosen": "eq 422",
11878 "depth": 0,
11879 "kind": "decide",
11880 "next": "same-currency",
11881 "node": "status",
11882 "observed": 422,
11883 "summary": "state.resp.status = 422"
11884 },
11885 {
11886 "depth": 0,
11887 "kind": "fail",
11888 "next": null,
11889 "node": "same-currency",
11890 "summary": "same-currency: USD to USD is the same currency."
11891 }
11892 ],
11893 "tree": "exchange-rate"
11894 },
11895 "tree": "exchange-rate"
11896 },
11897 "inputs": {
11898 "from": "usd",
11899 "to": "usd"
11900 },
11901 "intent": "exchange rate from usd to usd",
11902 "tree": "exchange-rate"
11903 },
11904 {
11905 "expect": {
11906 "inputs": {
11907 "word": "happy"
11908 },
11909 "output": {
11910 "synonyms": [
11911 "halcyon",
11912 "content",
11913 "bright",
11914 "felicitous",
11915 "riant",
11916 "expansive",
11917 "elysian",
11918 "joyful"
11919 ],
11920 "word": "happy"
11921 },
11922 "say": "Synonyms for happy: halcyon, content, bright, felicitous, riant, expansive, elysian, joyful.",
11923 "status": "done",
11924 "trace": {
11925 "gaps": [],
11926 "steps": [
11927 {
11928 "depth": 0,
11929 "kind": "intent",
11930 "next": "fetch",
11931 "node": "intent",
11932 "observed": {
11933 "cleaned": "synonyms for happy",
11934 "inputs": {
11935 "word": "happy"
11936 },
11937 "steps": [],
11938 "trigger": "synonyms (for|of) {word:word}"
11939 },
11940 "summary": "'synonyms for happy'; synonyms via 'synonyms (for|of) {word:word}' (13 literal chars)"
11941 },
11942 {
11943 "depth": 0,
11944 "kind": "observe",
11945 "next": "status",
11946 "node": "fetch",
11947 "observed": {
11948 "words": [
11949 "halcyon",
11950 "content",
11951 "bright",
11952 "felicitous",
11953 "riant",
11954 "expansive",
11955 "elysian",
11956 "joyful"
11957 ]
11958 },
11959 "summary": "GET https://api.datamuse.com/words?rel_syn=happy&max=8 -> 200; words=['halcyon', 'content', 'bright', 'felicitous', 'riant', 'expansive', 'elysian',…"
11960 },
11961 {
11962 "chosen": "eq 200",
11963 "depth": 0,
11964 "kind": "decide",
11965 "next": "any",
11966 "node": "status",
11967 "observed": 200,
11968 "summary": "state.resp.status = 200"
11969 },
11970 {
11971 "chosen": "else",
11972 "depth": 0,
11973 "kind": "decide",
11974 "next": "reply",
11975 "node": "any",
11976 "observed": [
11977 "halcyon",
11978 "content",
11979 "bright",
11980 "felicitous",
11981 "riant",
11982 "expansive",
11983 "elysian",
11984 "joyful"
11985 ],
11986 "summary": "state.words = ['halcyon', 'content', 'bright', 'felicitous', 'riant', 'expansive', 'elysian',…"
11987 },
11988 {
11989 "depth": 0,
11990 "kind": "done",
11991 "next": null,
11992 "node": "reply",
11993 "summary": "Synonyms for happy: halcyon, content, bright, felicitous, riant, expansive, elysian, joyful."
11994 }
11995 ],
11996 "tree": "synonyms"
11997 },
11998 "tree": "synonyms"
11999 },
12000 "inputs": {
12001 "word": "happy"
12002 },
12003 "intent": "synonyms for happy",
12004 "tree": "synonyms"
12005 },
12006 {
12007 "expect": {
12008 "inputs": {
12009 "word": "qqqzzz"
12010 },
12011 "reason": "no-synonyms",
12012 "say": "Datamuse knows no synonyms for qqqzzz.",
12013 "status": "failed",
12014 "trace": {
12015 "gaps": [],
12016 "steps": [
12017 {
12018 "depth": 0,
12019 "kind": "intent",
12020 "next": "fetch",
12021 "node": "intent",
12022 "observed": {
12023 "cleaned": "another word for qqqzzz",
12024 "inputs": {
12025 "word": "qqqzzz"
12026 },
12027 "steps": [],
12028 "trigger": "(another|other) word for {word:word}"
12029 },
12030 "summary": "'another word for qqqzzz'; synonyms via '(another|other) word for {word:word}' (19 literal chars)"
12031 },
12032 {
12033 "depth": 0,
12034 "kind": "observe",
12035 "next": "status",
12036 "node": "fetch",
12037 "observed": {
12038 "words": []
12039 },
12040 "summary": "GET https://api.datamuse.com/words?rel_syn=qqqzzz&max=8 -> 200; words=[]"
12041 },
12042 {
12043 "chosen": "eq 200",
12044 "depth": 0,
12045 "kind": "decide",
12046 "next": "any",
12047 "node": "status",
12048 "observed": 200,
12049 "summary": "state.resp.status = 200"
12050 },
12051 {
12052 "chosen": "empty True",
12053 "depth": 0,
12054 "kind": "decide",
12055 "next": "none",
12056 "node": "any",
12057 "observed": [],
12058 "summary": "state.words = []"
12059 },
12060 {
12061 "depth": 0,
12062 "kind": "fail",
12063 "next": null,
12064 "node": "none",
12065 "summary": "no-synonyms: Datamuse knows no synonyms for qqqzzz."
12066 }
12067 ],
12068 "tree": "synonyms"
12069 },
12070 "tree": "synonyms"
12071 },
12072 "inputs": {
12073 "word": "qqqzzz"
12074 },
12075 "intent": "another word for qqqzzz",
12076 "tree": "synonyms"
12077 },
12078 {
12079 "expect": {
12080 "inputs": {
12081 "place": "Oslo"
12082 },
12083 "output": {
12084 "country": "Norway",
12085 "date": "2026-10-29",
12086 "high": 9.4,
12087 "low": 5.9,
12088 "place": "Oslo",
12089 "rain_chance": 80,
12090 "sky": "rain",
12091 "unit": "°C"
12092 },
12093 "say": "Oslo, Norway, tomorrow (2026-10-29): rain, 5.9 to 9.4°C, 80% chance of rain.",
12094 "status": "done",
12095 "trace": {
12096 "gaps": [],
12097 "steps": [
12098 {
12099 "depth": 0,
12100 "kind": "intent",
12101 "next": "geocode",
12102 "node": "intent",
12103 "observed": {
12104 "cleaned": "weather in Oslo tomorrow",
12105 "inputs": {
12106 "place": "Oslo"
12107 },
12108 "steps": [],
12109 "trigger": "[what's the] weather (in|for) {place} tomorrow"
12110 },
12111 "summary": "'weather in Oslo tomorrow'; forecast via \"[what's the] weather (in|for) {place} tomorrow\" (29 literal chars)"
12112 },
12113 {
12114 "depth": 0,
12115 "kind": "observe",
12116 "next": "found",
12117 "node": "geocode",
12118 "observed": {
12119 "country": "Norway",
12120 "height": null,
12121 "hit": {
12122 "country": "Norway",
12123 "latitude": 59.91,
12124 "longitude": 10.75,
12125 "name": "Oslo"
12126 },
12127 "lat": 59.91,
12128 "lon": 10.75,
12129 "name": "Oslo",
12130 "zone": null
12131 },
12132 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Oslo&count=1&language=en&format=json -> 200; hit={'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}, lat=59.91, lon=10.75, name=Oslo, country=Norway, zone=None, height=None"
12133 },
12134 {
12135 "chosen": "exists True",
12136 "depth": 0,
12137 "kind": "decide",
12138 "next": "tomorrow",
12139 "node": "found",
12140 "observed": {
12141 "country": "Norway",
12142 "latitude": 59.91,
12143 "longitude": 10.75,
12144 "name": "Oslo"
12145 },
12146 "summary": "state.hit = {'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}"
12147 },
12148 {
12149 "depth": 0,
12150 "kind": "observe",
12151 "next": "have-day",
12152 "node": "tomorrow",
12153 "observed": {
12154 "code": 61,
12155 "day": "2026-10-29",
12156 "high": 9.4,
12157 "low": 5.9,
12158 "rain": 80,
12159 "unit": "°C"
12160 },
12161 "summary": "GET https://api.open-meteo.com/v1/forecast?latitude=59.91&longitude=10.75&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max&timezone=auto&forecast_days=2 -> 200; day=2026-10-29, code=61, high=9.4, low=5.9, rain=80, unit=°C"
12162 },
12163 {
12164 "chosen": "type 'number'",
12165 "depth": 0,
12166 "kind": "decide",
12167 "next": "sky",
12168 "node": "have-day",
12169 "observed": 9.4,
12170 "summary": "state.high = 9.4"
12171 },
12172 {
12173 "chosen": "between [51, 67]",
12174 "depth": 0,
12175 "kind": "decide",
12176 "next": "rain",
12177 "node": "sky",
12178 "observed": 61,
12179 "summary": "state.code = 61"
12180 },
12181 {
12182 "depth": 0,
12183 "kind": "set",
12184 "next": "reply",
12185 "node": "rain",
12186 "observed": {
12187 "sky": "rain"
12188 },
12189 "summary": "sky=rain"
12190 },
12191 {
12192 "depth": 0,
12193 "kind": "done",
12194 "next": null,
12195 "node": "reply",
12196 "summary": "Oslo, Norway, tomorrow (2026-10-29): rain, 5.9 to 9.4°C, 80% chance of rain."
12197 }
12198 ],
12199 "tree": "forecast"
12200 },
12201 "tree": "forecast"
12202 },
12203 "inputs": {
12204 "place": "Oslo"
12205 },
12206 "intent": "weather in Oslo tomorrow",
12207 "tree": "forecast"
12208 },
12209 {
12210 "expect": {
12211 "inputs": {
12212 "place": "Sydney"
12213 },
12214 "output": {
12215 "band": "extreme",
12216 "country": "Australia",
12217 "place": "Sydney",
12218 "uv_max": 11.2,
12219 "uv_now": 6.3
12220 },
12221 "say": "Sydney, Australia: UV index 6.3 now, up to 11.2 today (extreme): stay out of the midday sun.",
12222 "status": "done",
12223 "trace": {
12224 "gaps": [],
12225 "steps": [
12226 {
12227 "depth": 0,
12228 "kind": "intent",
12229 "next": "geocode",
12230 "node": "intent",
12231 "observed": {
12232 "cleaned": "uv index in Sydney",
12233 "inputs": {
12234 "place": "Sydney"
12235 },
12236 "steps": [],
12237 "trigger": "[what's the] (uv|uv index) in {place}"
12238 },
12239 "summary": "'uv index in Sydney'; uv-index via \"[what's the] (uv|uv index) in {place}\" (20 literal chars)"
12240 },
12241 {
12242 "depth": 0,
12243 "kind": "observe",
12244 "next": "found",
12245 "node": "geocode",
12246 "observed": {
12247 "country": "Australia",
12248 "height": 58.0,
12249 "hit": {
12250 "country": "Australia",
12251 "elevation": 58.0,
12252 "latitude": -33.86785,
12253 "longitude": 151.20732,
12254 "name": "Sydney",
12255 "timezone": "Australia/Sydney"
12256 },
12257 "lat": -33.86785,
12258 "lon": 151.20732,
12259 "name": "Sydney",
12260 "zone": "Australia/Sydney"
12261 },
12262 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Sydney&count=1&language=en&format=json -> 200; hit={'name': 'Sydney', 'country': 'Australia', 'latitude': -33.86785, 'longitude': …, lat=-33.86785, lon=151.20732, name=Sydney, country=Australia, zone=Australia/Sydney, height=58.0"
12263 },
12264 {
12265 "chosen": "exists True",
12266 "depth": 0,
12267 "kind": "decide",
12268 "next": "uv",
12269 "node": "found",
12270 "observed": {
12271 "country": "Australia",
12272 "elevation": 58.0,
12273 "latitude": -33.86785,
12274 "longitude": 151.20732,
12275 "name": "Sydney",
12276 "timezone": "Australia/Sydney"
12277 },
12278 "summary": "state.hit = {'name': 'Sydney', 'country': 'Australia', 'latitude': -33.86785, 'longitude': …"
12279 },
12280 {
12281 "depth": 0,
12282 "kind": "observe",
12283 "next": "band",
12284 "node": "uv",
12285 "observed": {
12286 "max": 11.2,
12287 "now": 6.3
12288 },
12289 "summary": "GET https://api.open-meteo.com/v1/forecast?latitude=-33.86785&longitude=151.20732&current=uv_index&daily=uv_index_max&timezone=auto&forecast_days=1 -> 200; now=6.3, max=11.2"
12290 },
12291 {
12292 "chosen": "gte 11",
12293 "depth": 0,
12294 "kind": "decide",
12295 "next": "extreme",
12296 "node": "band",
12297 "observed": 11.2,
12298 "summary": "state.max = 11.2"
12299 },
12300 {
12301 "depth": 0,
12302 "kind": "set",
12303 "next": "reply",
12304 "node": "extreme",
12305 "observed": {
12306 "advice": "stay out of the midday sun",
12307 "band": "extreme"
12308 },
12309 "summary": "band=extreme, advice=stay out of the midday sun"
12310 },
12311 {
12312 "depth": 0,
12313 "kind": "done",
12314 "next": null,
12315 "node": "reply",
12316 "summary": "Sydney, Australia: UV index 6.3 now, up to 11.2 today (extreme): stay out of the midday sun."
12317 }
12318 ],
12319 "tree": "uv-index"
12320 },
12321 "tree": "uv-index"
12322 },
12323 "inputs": {
12324 "place": "Sydney"
12325 },
12326 "intent": "uv index in Sydney",
12327 "tree": "uv-index"
12328 },
12329 {
12330 "expect": {
12331 "inputs": {
12332 "place": "La Paz"
12333 },
12334 "output": {
12335 "country": "Mexico",
12336 "date": "2026-10-27",
12337 "place": "La Paz",
12338 "time": "17:50",
12339 "timezone": "America/Mazatlan",
12340 "weekday": "Tuesday"
12341 },
12342 "say": "It is 17:50 on Tuesday 2026-10-27 in La Paz, Mexico (America/Mazatlan).",
12343 "status": "done",
12344 "trace": {
12345 "gaps": [],
12346 "steps": [
12347 {
12348 "depth": 0,
12349 "kind": "intent",
12350 "next": "geocode",
12351 "node": "intent",
12352 "observed": {
12353 "cleaned": "what time is it in La Paz",
12354 "inputs": {
12355 "place": "La Paz"
12356 },
12357 "steps": [],
12358 "trigger": "what time is it in {place}"
12359 },
12360 "summary": "'what time is it in La Paz'; local-time via 'what time is it in {place}' (14 literal chars)"
12361 },
12362 {
12363 "depth": 0,
12364 "kind": "observe",
12365 "next": "found",
12366 "node": "geocode",
12367 "observed": {
12368 "country": "Mexico",
12369 "height": 33.0,
12370 "hit": {
12371 "country": "Mexico",
12372 "elevation": 33.0,
12373 "latitude": 24.14231,
12374 "longitude": -110.31316,
12375 "name": "La Paz",
12376 "timezone": "America/Mazatlan"
12377 },
12378 "lat": 24.14231,
12379 "lon": -110.31316,
12380 "name": "La Paz",
12381 "zone": "America/Mazatlan"
12382 },
12383 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=La%20Paz&count=1&language=en&format=json -> 200; hit={'name': 'La Paz', 'country': 'Mexico', 'latitude': 24.14231, 'longitude': -110…, lat=24.14231, lon=-110.31316, name=La Paz, country=Mexico, zone=America/Mazatlan, height=33.0"
12384 },
12385 {
12386 "chosen": "exists True",
12387 "depth": 0,
12388 "kind": "decide",
12389 "next": "clock",
12390 "node": "found",
12391 "observed": {
12392 "country": "Mexico",
12393 "elevation": 33.0,
12394 "latitude": 24.14231,
12395 "longitude": -110.31316,
12396 "name": "La Paz",
12397 "timezone": "America/Mazatlan"
12398 },
12399 "summary": "state.hit = {'name': 'La Paz', 'country': 'Mexico', 'latitude': 24.14231, 'longitude': -110…"
12400 },
12401 {
12402 "depth": 0,
12403 "kind": "observe",
12404 "next": "status",
12405 "node": "clock",
12406 "observed": {
12407 "date": "2026-10-27",
12408 "time": "17:50",
12409 "weekday": "Tuesday"
12410 },
12411 "summary": "GET https://timeapi.io/api/time/current/zone?timeZone=America%2FMazatlan -> 200; time=17:50, weekday=Tuesday, date=2026-10-27"
12412 },
12413 {
12414 "chosen": "eq 200",
12415 "depth": 0,
12416 "kind": "decide",
12417 "next": "reply",
12418 "node": "status",
12419 "observed": 200,
12420 "summary": "state.resp.status = 200"
12421 },
12422 {
12423 "depth": 0,
12424 "kind": "done",
12425 "next": null,
12426 "node": "reply",
12427 "summary": "It is 17:50 on Tuesday 2026-10-27 in La Paz, Mexico (America/Mazatlan)."
12428 }
12429 ],
12430 "tree": "local-time"
12431 },
12432 "tree": "local-time"
12433 },
12434 "inputs": {
12435 "place": "La Paz"
12436 },
12437 "intent": "what time is it in La Paz",
12438 "tree": "local-time"
12439 },
12440 {
12441 "expect": {
12442 "inputs": {
12443 "place": "La Paz"
12444 },
12445 "output": {
12446 "country": "Mexico",
12447 "elevation_m": 33.0,
12448 "place": "La Paz"
12449 },
12450 "say": "La Paz, Mexico is 33 m above sea level.",
12451 "status": "done",
12452 "trace": {
12453 "gaps": [],
12454 "steps": [
12455 {
12456 "depth": 0,
12457 "kind": "intent",
12458 "next": "geocode",
12459 "node": "intent",
12460 "observed": {
12461 "cleaned": "how high is La Paz",
12462 "inputs": {
12463 "place": "La Paz"
12464 },
12465 "steps": [],
12466 "trigger": "how high (is|above sea level is) {place}"
12467 },
12468 "summary": "'how high is La Paz'; elevation via 'how high (is|above sea level is) {place}' (24 literal chars)"
12469 },
12470 {
12471 "depth": 0,
12472 "kind": "observe",
12473 "next": "found",
12474 "node": "geocode",
12475 "observed": {
12476 "country": "Mexico",
12477 "height": 33.0,
12478 "hit": {
12479 "country": "Mexico",
12480 "elevation": 33.0,
12481 "latitude": 24.14231,
12482 "longitude": -110.31316,
12483 "name": "La Paz",
12484 "timezone": "America/Mazatlan"
12485 },
12486 "lat": 24.14231,
12487 "lon": -110.31316,
12488 "name": "La Paz",
12489 "zone": "America/Mazatlan"
12490 },
12491 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=La%20Paz&count=1&language=en&format=json -> 200; hit={'name': 'La Paz', 'country': 'Mexico', 'latitude': 24.14231, 'longitude': -110…, lat=24.14231, lon=-110.31316, name=La Paz, country=Mexico, zone=America/Mazatlan, height=33.0"
12492 },
12493 {
12494 "chosen": "exists True",
12495 "depth": 0,
12496 "kind": "decide",
12497 "next": "has-height",
12498 "node": "found",
12499 "observed": {
12500 "country": "Mexico",
12501 "elevation": 33.0,
12502 "latitude": 24.14231,
12503 "longitude": -110.31316,
12504 "name": "La Paz",
12505 "timezone": "America/Mazatlan"
12506 },
12507 "summary": "state.hit = {'name': 'La Paz', 'country': 'Mexico', 'latitude': 24.14231, 'longitude': -110…"
12508 },
12509 {
12510 "chosen": "type 'number'",
12511 "depth": 0,
12512 "kind": "decide",
12513 "next": "reply",
12514 "node": "has-height",
12515 "observed": 33.0,
12516 "summary": "state.height = 33.0"
12517 },
12518 {
12519 "depth": 0,
12520 "kind": "done",
12521 "next": null,
12522 "node": "reply",
12523 "summary": "La Paz, Mexico is 33 m above sea level."
12524 }
12525 ],
12526 "tree": "elevation"
12527 },
12528 "tree": "elevation"
12529 },
12530 "inputs": {
12531 "place": "La Paz"
12532 },
12533 "intent": "how high is La Paz",
12534 "tree": "elevation"
12535 },
12536 {
12537 "expect": {
12538 "inputs": {
12539 "place": "Oslo"
12540 },
12541 "reason": "no-height",
12542 "say": "Open-Meteo knows Oslo but not how high it is.",
12543 "status": "failed",
12544 "trace": {
12545 "gaps": [],
12546 "steps": [
12547 {
12548 "depth": 0,
12549 "kind": "intent",
12550 "next": "geocode",
12551 "node": "intent",
12552 "observed": {
12553 "cleaned": "how high is Oslo",
12554 "inputs": {
12555 "place": "Oslo"
12556 },
12557 "steps": [],
12558 "trigger": "how high (is|above sea level is) {place}"
12559 },
12560 "summary": "'how high is Oslo'; elevation via 'how high (is|above sea level is) {place}' (24 literal chars)"
12561 },
12562 {
12563 "depth": 0,
12564 "kind": "observe",
12565 "next": "found",
12566 "node": "geocode",
12567 "observed": {
12568 "country": "Norway",
12569 "height": null,
12570 "hit": {
12571 "country": "Norway",
12572 "latitude": 59.91,
12573 "longitude": 10.75,
12574 "name": "Oslo"
12575 },
12576 "lat": 59.91,
12577 "lon": 10.75,
12578 "name": "Oslo",
12579 "zone": null
12580 },
12581 "summary": "GET https://geocoding-api.open-meteo.com/v1/search?name=Oslo&count=1&language=en&format=json -> 200; hit={'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}, lat=59.91, lon=10.75, name=Oslo, country=Norway, zone=None, height=None"
12582 },
12583 {
12584 "chosen": "exists True",
12585 "depth": 0,
12586 "kind": "decide",
12587 "next": "has-height",
12588 "node": "found",
12589 "observed": {
12590 "country": "Norway",
12591 "latitude": 59.91,
12592 "longitude": 10.75,
12593 "name": "Oslo"
12594 },
12595 "summary": "state.hit = {'name': 'Oslo', 'country': 'Norway', 'latitude': 59.91, 'longitude': 10.75}"
12596 },
12597 {
12598 "chosen": "else",
12599 "depth": 0,
12600 "kind": "decide",
12601 "next": "no-height",
12602 "node": "has-height",
12603 "summary": "state.height = <missing>"
12604 },
12605 {
12606 "depth": 0,
12607 "kind": "fail",
12608 "next": null,
12609 "node": "no-height",
12610 "summary": "no-height: Open-Meteo knows Oslo but not how high it is."
12611 }
12612 ],
12613 "tree": "elevation"
12614 },
12615 "tree": "elevation"
12616 },
12617 "inputs": {
12618 "place": "Oslo"
12619 },
12620 "intent": "how high is Oslo",
12621 "tree": "elevation"
12622 },
12623 {
12624 "expect": {
12625 "inputs": {
12626 "code": "90210",
12627 "country": "us"
12628 },
12629 "output": {
12630 "code": "90210",
12631 "country": "United States",
12632 "latitude": 34.0901,
12633 "longitude": -118.4065,
12634 "place": "Beverly Hills",
12635 "region": "California"
12636 },
12637 "say": "90210 is Beverly Hills, California, United States.",
12638 "status": "done",
12639 "trace": {
12640 "gaps": [],
12641 "steps": [
12642 {
12643 "depth": 0,
12644 "kind": "intent",
12645 "next": "fetch",
12646 "node": "intent",
12647 "observed": {
12648 "cleaned": "where is zip 90210",
12649 "inputs": {
12650 "code": "90210",
12651 "country": "us"
12652 },
12653 "steps": [],
12654 "trigger": "where is zip [code] {code:word}"
12655 },
12656 "summary": "'where is zip 90210'; postal-code via 'where is zip [code] {code:word}' (14 literal chars)"
12657 },
12658 {
12659 "depth": 0,
12660 "kind": "observe",
12661 "next": "status",
12662 "node": "fetch",
12663 "observed": {
12664 "country_name": "United States",
12665 "lat": "34.0901",
12666 "lon": "-118.4065",
12667 "place": "Beverly Hills",
12668 "region": "California"
12669 },
12670 "summary": "GET https://api.zippopotam.us/us/90210 -> 200; place=Beverly Hills, region=California, country_name=United States, lat=34.0901, lon=-118.4065"
12671 },
12672 {
12673 "chosen": "eq 200",
12674 "depth": 0,
12675 "kind": "decide",
12676 "next": "reply",
12677 "node": "status",
12678 "observed": 200,
12679 "summary": "state.resp.status = 200"
12680 },
12681 {
12682 "depth": 0,
12683 "kind": "done",
12684 "next": null,
12685 "node": "reply",
12686 "summary": "90210 is Beverly Hills, California, United States."
12687 }
12688 ],
12689 "tree": "postal-code"
12690 },
12691 "tree": "postal-code"
12692 },
12693 "inputs": {
12694 "code": "90210",
12695 "country": "us"
12696 },
12697 "intent": "where is zip 90210",
12698 "tree": "postal-code"
12699 },
12700 {
12701 "expect": {
12702 "inputs": {
12703 "code": "SW1A",
12704 "country": "GB"
12705 },
12706 "output": {
12707 "code": "SW1A",
12708 "country": "Great Britain",
12709 "latitude": 51.4841,
12710 "longitude": -0.1966,
12711 "place": "Westminster Abbey",
12712 "region": "England"
12713 },
12714 "say": "SW1A is Westminster Abbey, England, Great Britain.",
12715 "status": "done",
12716 "trace": {
12717 "gaps": [],
12718 "steps": [
12719 {
12720 "depth": 0,
12721 "kind": "intent",
12722 "next": "fetch",
12723 "node": "intent",
12724 "observed": {
12725 "cleaned": "postcode SW1A in GB",
12726 "inputs": {
12727 "code": "SW1A",
12728 "country": "GB"
12729 },
12730 "steps": [],
12731 "trigger": "[where is] (postcode|postal code) {code:word} in {country:word}"
12732 },
12733 "summary": "'postcode SW1A in GB'; postal-code via '[where is] (postcode|postal code) {code:word} in {country:word}' (27 literal chars)"
12734 },
12735 {
12736 "depth": 0,
12737 "kind": "observe",
12738 "next": "status",
12739 "node": "fetch",
12740 "observed": {
12741 "country_name": "Great Britain",
12742 "lat": "51.4841",
12743 "lon": "-0.1966",
12744 "place": "Westminster Abbey",
12745 "region": "England"
12746 },
12747 "summary": "GET https://api.zippopotam.us/gb/SW1A -> 200; place=Westminster Abbey, region=England, country_name=Great Britain, lat=51.4841, lon=-0.1966"
12748 },
12749 {
12750 "chosen": "eq 200",
12751 "depth": 0,
12752 "kind": "decide",
12753 "next": "reply",
12754 "node": "status",
12755 "observed": 200,
12756 "summary": "state.resp.status = 200"
12757 },
12758 {
12759 "depth": 0,
12760 "kind": "done",
12761 "next": null,
12762 "node": "reply",
12763 "summary": "SW1A is Westminster Abbey, England, Great Britain."
12764 }
12765 ],
12766 "tree": "postal-code"
12767 },
12768 "tree": "postal-code"
12769 },
12770 "inputs": {
12771 "code": "SW1A",
12772 "country": "GB"
12773 },
12774 "intent": "postcode SW1A in GB",
12775 "tree": "postal-code"
12776 },
12777 {
12778 "expect": {
12779 "inputs": {
12780 "code": "00000",
12781 "country": "us"
12782 },
12783 "reason": "unknown-code",
12784 "say": "Zippopotam.us knows no postal code 00000 in US.",
12785 "status": "failed",
12786 "trace": {
12787 "gaps": [],
12788 "steps": [
12789 {
12790 "depth": 0,
12791 "kind": "intent",
12792 "next": "fetch",
12793 "node": "intent",
12794 "observed": {
12795 "cleaned": "where is zip 00000",
12796 "inputs": {
12797 "code": "00000",
12798 "country": "us"
12799 },
12800 "steps": [],
12801 "trigger": "where is zip [code] {code:word}"
12802 },
12803 "summary": "'where is zip 00000'; postal-code via 'where is zip [code] {code:word}' (14 literal chars)"
12804 },
12805 {
12806 "depth": 0,
12807 "kind": "observe",
12808 "next": "status",
12809 "node": "fetch",
12810 "observed": {
12811 "country_name": null,
12812 "lat": null,
12813 "lon": null,
12814 "place": null,
12815 "region": null
12816 },
12817 "summary": "GET https://api.zippopotam.us/us/00000 -> 404; place=None, region=None, country_name=None, lat=None, lon=None"
12818 },
12819 {
12820 "chosen": "eq 404",
12821 "depth": 0,
12822 "kind": "decide",
12823 "next": "unknown-code",
12824 "node": "status",
12825 "observed": 404,
12826 "summary": "state.resp.status = 404"
12827 },
12828 {
12829 "depth": 0,
12830 "kind": "fail",
12831 "next": null,
12832 "node": "unknown-code",
12833 "summary": "unknown-code: Zippopotam.us knows no postal code 00000 in US."
12834 }
12835 ],
12836 "tree": "postal-code"
12837 },
12838 "tree": "postal-code"
12839 },
12840 "inputs": {
12841 "code": "00000",
12842 "country": "us"
12843 },
12844 "intent": "where is zip 00000",
12845 "tree": "postal-code"
12846 },
12847 {
12848 "expect": {
12849 "inputs": {
12850 "site": "example.com"
12851 },
12852 "output": {
12853 "site": "example.com",
12854 "timestamp": "20261027205234",
12855 "url": "http://web.archive.org/web/20261027205234/https://example.com/"
12856 },
12857 "say": "The Wayback Machine has example.com. Its latest copy: http://web.archive.org/web/20261027205234/https://example.com/",
12858 "status": "done",
12859 "trace": {
12860 "gaps": [],
12861 "steps": [
12862 {
12863 "depth": 0,
12864 "kind": "intent",
12865 "next": "fetch",
12866 "node": "intent",
12867 "observed": {
12868 "cleaned": "is example.com archived",
12869 "inputs": {
12870 "site": "example.com"
12871 },
12872 "steps": [],
12873 "trigger": "is {site:host} archived"
12874 },
12875 "summary": "'is example.com archived'; wayback via 'is {site:host} archived' (10 literal chars)"
12876 },
12877 {
12878 "depth": 0,
12879 "kind": "observe",
12880 "next": "status",
12881 "node": "fetch",
12882 "observed": {
12883 "copy": "http://web.archive.org/web/20261027205234/https://example.com/",
12884 "stamp": "20261027205234"
12885 },
12886 "summary": "GET https://archive.org/wayback/available?url=example.com -> 200; copy=http://web.archive.org/web/20261027205234/https://example.com/, stamp=20261027205234"
12887 },
12888 {
12889 "chosen": "eq 200",
12890 "depth": 0,
12891 "kind": "decide",
12892 "next": "any",
12893 "node": "status",
12894 "observed": 200,
12895 "summary": "state.resp.status = 200"
12896 },
12897 {
12898 "chosen": "exists True",
12899 "depth": 0,
12900 "kind": "decide",
12901 "next": "reply",
12902 "node": "any",
12903 "observed": "http://web.archive.org/web/20261027205234/https://example.com/",
12904 "summary": "state.copy = http://web.archive.org/web/20261027205234/https://example.com/"
12905 },
12906 {
12907 "depth": 0,
12908 "kind": "done",
12909 "next": null,
12910 "node": "reply",
12911 "summary": "The Wayback Machine has example.com. Its latest copy: http://web.archive.org/web/20261027205234/https://example.com/"
12912 }
12913 ],
12914 "tree": "wayback"
12915 },
12916 "tree": "wayback"
12917 },
12918 "inputs": {
12919 "site": "example.com"
12920 },
12921 "intent": "is example.com archived",
12922 "tree": "wayback"
12923 },
12924 {
12925 "expect": {
12926 "inputs": {
12927 "site": "nothing-here-qqzz.example"
12928 },
12929 "output": {
12930 "site": "nothing-here-qqzz.example",
12931 "url": ""
12932 },
12933 "say": "The Wayback Machine has no copy of nothing-here-qqzz.example.",
12934 "status": "done",
12935 "trace": {
12936 "gaps": [],
12937 "steps": [
12938 {
12939 "depth": 0,
12940 "kind": "intent",
12941 "next": "fetch",
12942 "node": "intent",
12943 "observed": {
12944 "cleaned": "is nothing-here-qqzz.example archived",
12945 "inputs": {
12946 "site": "nothing-here-qqzz.example"
12947 },
12948 "steps": [],
12949 "trigger": "is {site:host} archived"
12950 },
12951 "summary": "'is nothing-here-qqzz.example archived'; wayback via 'is {site:host} archived' (10 literal chars)"
12952 },
12953 {
12954 "depth": 0,
12955 "kind": "observe",
12956 "next": "status",
12957 "node": "fetch",
12958 "observed": {
12959 "copy": null,
12960 "stamp": null
12961 },
12962 "summary": "GET https://archive.org/wayback/available?url=nothing-here-qqzz.example -> 200; copy=None, stamp=None"
12963 },
12964 {
12965 "chosen": "eq 200",
12966 "depth": 0,
12967 "kind": "decide",
12968 "next": "any",
12969 "node": "status",
12970 "observed": 200,
12971 "summary": "state.resp.status = 200"
12972 },
12973 {
12974 "chosen": "else",
12975 "depth": 0,
12976 "kind": "decide",
12977 "next": "none",
12978 "node": "any",
12979 "summary": "state.copy = <missing>"
12980 },
12981 {
12982 "depth": 0,
12983 "kind": "done",
12984 "next": null,
12985 "node": "none",
12986 "summary": "The Wayback Machine has no copy of nothing-here-qqzz.example."
12987 }
12988 ],
12989 "tree": "wayback"
12990 },
12991 "tree": "wayback"
12992 },
12993 "inputs": {
12994 "site": "nothing-here-qqzz.example"
12995 },
12996 "intent": "is nothing-here-qqzz.example archived",
12997 "tree": "wayback"
12998 },
12999 {
13000 "expect": {
13001 "inputs": {
13002 "word": "cat"
13003 },
13004 "output": {
13005 "rhymes": [
13006 "flat",
13007 "caveat",
13008 "brat",
13009 "pat",
13010 "combat",
13011 "slat",
13012 "that",
13013 "rat",
13014 "diplomat",
13015 "fat"
13016 ],
13017 "word": "cat"
13018 },
13019 "say": "Rhymes with cat: flat, caveat, brat, pat, combat, slat, that, rat, diplomat, fat.",
13020 "status": "done",
13021 "trace": {
13022 "gaps": [],
13023 "steps": [
13024 {
13025 "depth": 0,
13026 "kind": "intent",
13027 "next": "fetch",
13028 "node": "intent",
13029 "observed": {
13030 "cleaned": "what rhymes with cat",
13031 "inputs": {
13032 "word": "cat"
13033 },
13034 "steps": [],
13035 "trigger": "what rhymes with {word:word}"
13036 },
13037 "summary": "'what rhymes with cat'; rhymes via 'what rhymes with {word:word}' (14 literal chars)"
13038 },
13039 {
13040 "depth": 0,
13041 "kind": "observe",
13042 "next": "status",
13043 "node": "fetch",
13044 "observed": {
13045 "words": [
13046 "flat",
13047 "caveat",
13048 "brat",
13049 "pat",
13050 "combat",
13051 "slat",
13052 "that",
13053 "rat",
13054 "diplomat",
13055 "fat"
13056 ]
13057 },
13058 "summary": "GET https://api.datamuse.com/words?rel_rhy=cat&max=10 -> 200; words=['flat', 'caveat', 'brat', 'pat', 'combat', 'slat', 'that', 'rat', 'diplomat', …"
13059 },
13060 {
13061 "chosen": "eq 200",
13062 "depth": 0,
13063 "kind": "decide",
13064 "next": "any",
13065 "node": "status",
13066 "observed": 200,
13067 "summary": "state.resp.status = 200"
13068 },
13069 {
13070 "chosen": "else",
13071 "depth": 0,
13072 "kind": "decide",
13073 "next": "reply",
13074 "node": "any",
13075 "observed": [
13076 "flat",
13077 "caveat",
13078 "brat",
13079 "pat",
13080 "combat",
13081 "slat",
13082 "that",
13083 "rat",
13084 "diplomat",
13085 "fat"
13086 ],
13087 "summary": "state.words = ['flat', 'caveat', 'brat', 'pat', 'combat', 'slat', 'that', 'rat', 'diplomat', …"
13088 },
13089 {
13090 "depth": 0,
13091 "kind": "done",
13092 "next": null,
13093 "node": "reply",
13094 "summary": "Rhymes with cat: flat, caveat, brat, pat, combat, slat, that, rat, diplomat, fat."
13095 }
13096 ],
13097 "tree": "rhymes"
13098 },
13099 "tree": "rhymes"
13100 },
13101 "inputs": {
13102 "word": "cat"
13103 },
13104 "intent": "what rhymes with cat",
13105 "tree": "rhymes"
13106 },
13107 {
13108 "expect": {
13109 "inputs": {
13110 "word": "qqzzx"
13111 },
13112 "reason": "no-rhymes",
13113 "say": "Datamuse knows nothing that rhymes with qqzzx.",
13114 "status": "failed",
13115 "trace": {
13116 "gaps": [],
13117 "steps": [
13118 {
13119 "depth": 0,
13120 "kind": "intent",
13121 "next": "fetch",
13122 "node": "intent",
13123 "observed": {
13124 "cleaned": "what rhymes with qqzzx",
13125 "inputs": {
13126 "word": "qqzzx"
13127 },
13128 "steps": [],
13129 "trigger": "what rhymes with {word:word}"
13130 },
13131 "summary": "'what rhymes with qqzzx'; rhymes via 'what rhymes with {word:word}' (14 literal chars)"
13132 },
13133 {
13134 "depth": 0,
13135 "kind": "observe",
13136 "next": "status",
13137 "node": "fetch",
13138 "observed": {
13139 "words": []
13140 },
13141 "summary": "GET https://api.datamuse.com/words?rel_rhy=qqzzx&max=10 -> 200; words=[]"
13142 },
13143 {
13144 "chosen": "eq 200",
13145 "depth": 0,
13146 "kind": "decide",
13147 "next": "any",
13148 "node": "status",
13149 "observed": 200,
13150 "summary": "state.resp.status = 200"
13151 },
13152 {
13153 "chosen": "empty True",
13154 "depth": 0,
13155 "kind": "decide",
13156 "next": "none",
13157 "node": "any",
13158 "observed": [],
13159 "summary": "state.words = []"
13160 },
13161 {
13162 "depth": 0,
13163 "kind": "fail",
13164 "next": null,
13165 "node": "none",
13166 "summary": "no-rhymes: Datamuse knows nothing that rhymes with qqzzx."
13167 }
13168 ],
13169 "tree": "rhymes"
13170 },
13171 "tree": "rhymes"
13172 },
13173 "inputs": {
13174 "word": "qqzzx"
13175 },
13176 "intent": "what rhymes with qqzzx",
13177 "tree": "rhymes"
13178 },
13179 {
13180 "expect": {
13181 "inputs": {
13182 "repo": "astral-sh/uv"
13183 },
13184 "output": {
13185 "archived": false,
13186 "description": "An extremely fast Python package and project manager, written in Rust.",
13187 "forks": 2210,
13188 "language": "Rust",
13189 "open_issues": 2004,
13190 "repo": "astral-sh/uv",
13191 "stars": 71234,
13192 "url": "https://github.com/astral-sh/uv"
13193 },
13194 "say": "astral-sh/uv: An extremely fast Python package and project manager, written in Rust. (Rust) · 71234 stars, 2210 forks, 2004 open issues. https://github.com/astral-sh/uv",
13195 "status": "done",
13196 "trace": {
13197 "gaps": [],
13198 "steps": [
13199 {
13200 "depth": 0,
13201 "kind": "intent",
13202 "next": "fetch",
13203 "node": "intent",
13204 "observed": {
13205 "cleaned": "how many stars does astral-sh/uv have",
13206 "inputs": {
13207 "repo": "astral-sh/uv"
13208 },
13209 "steps": [],
13210 "trigger": "how many stars does {repo:word} have"
13211 },
13212 "summary": "'how many stars does astral-sh/uv have'; github-repo via 'how many stars does {repo:word} have' (20 literal chars)"
13213 },
13214 {
13215 "depth": 0,
13216 "kind": "observe",
13217 "next": "status",
13218 "node": "fetch",
13219 "observed": {
13220 "about": "An extremely fast Python package and project manager, written in Rust.",
13221 "archived": false,
13222 "forks": 2210,
13223 "full": "astral-sh/uv",
13224 "issues": 2004,
13225 "language": "Rust",
13226 "stars": 71234,
13227 "url": "https://github.com/astral-sh/uv"
13228 },
13229 "summary": "GET https://api.github.com/repos/astral-sh/uv -> 200; full=astral-sh/uv, about=An extremely fast Python package and project manager, written in Rust., language=Rust, stars=71234, forks=2210, issues=2004, url=https://github.com/astral-sh/uv, archived=False"
13230 },
13231 {
13232 "chosen": "eq 200",
13233 "depth": 0,
13234 "kind": "decide",
13235 "next": "reply",
13236 "node": "status",
13237 "observed": 200,
13238 "summary": "state.resp.status = 200"
13239 },
13240 {
13241 "depth": 0,
13242 "kind": "done",
13243 "next": null,
13244 "node": "reply",
13245 "summary": "astral-sh/uv: An extremely fast Python package and project manager, written in Rust. (Rust) · 71234 stars, 2210 forks, 2004 open issues. https://github.com/astral-sh/uv"
13246 }
13247 ],
13248 "tree": "github-repo"
13249 },
13250 "tree": "github-repo"
13251 },
13252 "inputs": {
13253 "repo": "astral-sh/uv"
13254 },
13255 "intent": "how many stars does astral-sh/uv have",
13256 "tree": "github-repo"
13257 },
13258 {
13259 "expect": {
13260 "inputs": {
13261 "repo": "b-i-t-8/nothing-here"
13262 },
13263 "reason": "no-repo",
13264 "say": "GitHub has no public repository b-i-t-8/nothing-here.",
13265 "status": "failed",
13266 "trace": {
13267 "gaps": [],
13268 "steps": [
13269 {
13270 "depth": 0,
13271 "kind": "intent",
13272 "next": "fetch",
13273 "node": "intent",
13274 "observed": {
13275 "cleaned": "github repo b-i-t-8/nothing-here",
13276 "inputs": {
13277 "repo": "b-i-t-8/nothing-here"
13278 },
13279 "steps": [],
13280 "trigger": "github (repo|repository) {repo:word}"
13281 },
13282 "summary": "'github repo b-i-t-8/nothing-here'; github-repo via 'github (repo|repository) {repo:word}' (20 literal chars)"
13283 },
13284 {
13285 "depth": 0,
13286 "kind": "observe",
13287 "next": "status",
13288 "node": "fetch",
13289 "observed": {
13290 "about": null,
13291 "archived": null,
13292 "forks": null,
13293 "full": null,
13294 "issues": null,
13295 "language": null,
13296 "stars": null,
13297 "url": null
13298 },
13299 "summary": "GET https://api.github.com/repos/b-i-t-8/nothing-here -> 404; full=None, about=None, language=None, stars=None, forks=None, issues=None, url=None, archived=None"
13300 },
13301 {
13302 "chosen": "eq 404",
13303 "depth": 0,
13304 "kind": "decide",
13305 "next": "no-repo",
13306 "node": "status",
13307 "observed": 404,
13308 "summary": "state.resp.status = 404"
13309 },
13310 {
13311 "depth": 0,
13312 "kind": "fail",
13313 "next": null,
13314 "node": "no-repo",
13315 "summary": "no-repo: GitHub has no public repository b-i-t-8/nothing-here."
13316 }
13317 ],
13318 "tree": "github-repo"
13319 },
13320 "tree": "github-repo"
13321 },
13322 "inputs": {
13323 "repo": "b-i-t-8/nothing-here"
13324 },
13325 "intent": "github repo b-i-t-8/nothing-here",
13326 "tree": "github-repo"
13327 },
13328 {
13329 "expect": {
13330 "inputs": {
13331 "module": "github.com/gorilla/mux"
13332 },
13333 "output": {
13334 "module": "github.com/gorilla/mux",
13335 "published": "2023-10-18",
13336 "url": "https://pkg.go.dev/github.com/gorilla/mux",
13337 "version": "v1.8.1"
13338 },
13339 "say": "github.com/gorilla/mux v1.8.1, published 2023-10-18. https://pkg.go.dev/github.com/gorilla/mux",
13340 "status": "done",
13341 "trace": {
13342 "gaps": [],
13343 "steps": [
13344 {
13345 "depth": 0,
13346 "kind": "intent",
13347 "next": "fetch",
13348 "node": "intent",
13349 "observed": {
13350 "cleaned": "latest version of github.com/gorilla/mux on go",
13351 "inputs": {
13352 "module": "github.com/gorilla/mux"
13353 },
13354 "steps": [],
13355 "trigger": "[what's the] latest version of {module:word} on go"
13356 },
13357 "summary": "'latest version of github.com/gorilla/mux on go'; go-module via \"[what's the] latest version of {module:word} on go\" (28 literal chars)"
13358 },
13359 {
13360 "depth": 0,
13361 "kind": "observe",
13362 "next": "status",
13363 "node": "fetch",
13364 "observed": {
13365 "day": "2023-10-18",
13366 "version": "v1.8.1"
13367 },
13368 "summary": "GET https://proxy.golang.org/github.com/gorilla/mux/@latest -> 200; version=v1.8.1, day=2023-10-18"
13369 },
13370 {
13371 "chosen": "eq 200",
13372 "depth": 0,
13373 "kind": "decide",
13374 "next": "reply",
13375 "node": "status",
13376 "observed": 200,
13377 "summary": "state.resp.status = 200"
13378 },
13379 {
13380 "depth": 0,
13381 "kind": "done",
13382 "next": null,
13383 "node": "reply",
13384 "summary": "github.com/gorilla/mux v1.8.1, published 2023-10-18. https://pkg.go.dev/github.com/gorilla/mux"
13385 }
13386 ],
13387 "tree": "go-module"
13388 },
13389 "tree": "go-module"
13390 },
13391 "inputs": {
13392 "module": "github.com/gorilla/mux"
13393 },
13394 "intent": "latest version of github.com/gorilla/mux on go",
13395 "tree": "go-module"
13396 },
13397 {
13398 "expect": {
13399 "inputs": {
13400 "module": "github.com/nope/nope-qq"
13401 },
13402 "reason": "no-module",
13403 "say": "The Go module proxy has no module github.com/nope/nope-qq.",
13404 "status": "failed",
13405 "trace": {
13406 "gaps": [],
13407 "steps": [
13408 {
13409 "depth": 0,
13410 "kind": "intent",
13411 "next": "fetch",
13412 "node": "intent",
13413 "observed": {
13414 "cleaned": "go module github.com/nope/nope-qq",
13415 "inputs": {
13416 "module": "github.com/nope/nope-qq"
13417 },
13418 "steps": [],
13419 "trigger": "go (module|version) {module:word}"
13420 },
13421 "summary": "'go module github.com/nope/nope-qq'; go-module via 'go (module|version) {module:word}' (15 literal chars)"
13422 },
13423 {
13424 "depth": 0,
13425 "kind": "observe",
13426 "next": "status",
13427 "node": "fetch",
13428 "observed": {
13429 "day": null,
13430 "version": null
13431 },
13432 "summary": "GET https://proxy.golang.org/github.com/nope/nope-qq/@latest -> 404; version=None, day=None"
13433 },
13434 {
13435 "chosen": "in [404, 410]",
13436 "depth": 0,
13437 "kind": "decide",
13438 "next": "no-module",
13439 "node": "status",
13440 "observed": 404,
13441 "summary": "state.resp.status = 404"
13442 },
13443 {
13444 "depth": 0,
13445 "kind": "fail",
13446 "next": null,
13447 "node": "no-module",
13448 "summary": "no-module: The Go module proxy has no module github.com/nope/nope-qq."
13449 }
13450 ],
13451 "tree": "go-module"
13452 },
13453 "tree": "go-module"
13454 },
13455 "inputs": {
13456 "module": "github.com/nope/nope-qq"
13457 },
13458 "intent": "go module github.com/nope/nope-qq",
13459 "tree": "go-module"
13460 },
13461 {
13462 "expect": {
13463 "inputs": {
13464 "formula": "wget"
13465 },
13466 "output": {
13467 "description": "Internet file retriever",
13468 "formula": "wget",
13469 "homepage": "https://www.gnu.org/software/wget/",
13470 "install": "brew install wget",
13471 "version": "1.25.0"
13472 },
13473 "say": "wget 1.25.0 on Homebrew: Internet file retriever. https://www.gnu.org/software/wget/",
13474 "status": "done",
13475 "trace": {
13476 "gaps": [],
13477 "steps": [
13478 {
13479 "depth": 0,
13480 "kind": "intent",
13481 "next": "fetch",
13482 "node": "intent",
13483 "observed": {
13484 "cleaned": "brew formula wget",
13485 "inputs": {
13486 "formula": "wget"
13487 },
13488 "steps": [],
13489 "trigger": "(brew|homebrew) (formula|version) {formula:word}"
13490 },
13491 "summary": "'brew formula wget'; homebrew via '(brew|homebrew) (formula|version) {formula:word}' (26 literal chars)"
13492 },
13493 {
13494 "depth": 0,
13495 "kind": "observe",
13496 "next": "status",
13497 "node": "fetch",
13498 "observed": {
13499 "about": "Internet file retriever",
13500 "home": "https://www.gnu.org/software/wget/",
13501 "name": "wget",
13502 "version": "1.25.0"
13503 },
13504 "summary": "GET https://formulae.brew.sh/api/formula/wget.json -> 200; name=wget, version=1.25.0, about=Internet file retriever, home=https://www.gnu.org/software/wget/"
13505 },
13506 {
13507 "chosen": "eq 200",
13508 "depth": 0,
13509 "kind": "decide",
13510 "next": "reply",
13511 "node": "status",
13512 "observed": 200,
13513 "summary": "state.resp.status = 200"
13514 },
13515 {
13516 "depth": 0,
13517 "kind": "done",
13518 "next": null,
13519 "node": "reply",
13520 "summary": "wget 1.25.0 on Homebrew: Internet file retriever. https://www.gnu.org/software/wget/"
13521 }
13522 ],
13523 "tree": "homebrew"
13524 },
13525 "tree": "homebrew"
13526 },
13527 "inputs": {
13528 "formula": "wget"
13529 },
13530 "intent": "brew formula wget",
13531 "tree": "homebrew"
13532 },
13533 {
13534 "expect": {
13535 "inputs": {
13536 "formula": "nope-qqzz"
13537 },
13538 "reason": "no-formula",
13539 "say": "Homebrew has no formula called nope-qqzz.",
13540 "status": "failed",
13541 "trace": {
13542 "gaps": [],
13543 "steps": [
13544 {
13545 "depth": 0,
13546 "kind": "intent",
13547 "next": "fetch",
13548 "node": "intent",
13549 "observed": {
13550 "cleaned": "brew formula nope-qqzz",
13551 "inputs": {
13552 "formula": "nope-qqzz"
13553 },
13554 "steps": [],
13555 "trigger": "(brew|homebrew) (formula|version) {formula:word}"
13556 },
13557 "summary": "'brew formula nope-qqzz'; homebrew via '(brew|homebrew) (formula|version) {formula:word}' (26 literal chars)"
13558 },
13559 {
13560 "depth": 0,
13561 "kind": "observe",
13562 "next": "status",
13563 "node": "fetch",
13564 "observed": {
13565 "about": null,
13566 "home": null,
13567 "name": null,
13568 "version": null
13569 },
13570 "summary": "GET https://formulae.brew.sh/api/formula/nope-qqzz.json -> 404; name=None, version=None, about=None, home=None"
13571 },
13572 {
13573 "chosen": "eq 404",
13574 "depth": 0,
13575 "kind": "decide",
13576 "next": "no-formula",
13577 "node": "status",
13578 "observed": 404,
13579 "summary": "state.resp.status = 404"
13580 },
13581 {
13582 "depth": 0,
13583 "kind": "fail",
13584 "next": null,
13585 "node": "no-formula",
13586 "summary": "no-formula: Homebrew has no formula called nope-qqzz."
13587 }
13588 ],
13589 "tree": "homebrew"
13590 },
13591 "tree": "homebrew"
13592 },
13593 "inputs": {
13594 "formula": "nope-qqzz"
13595 },
13596 "intent": "brew formula nope-qqzz",
13597 "tree": "homebrew"
13598 },
13599 {
13600 "expect": {
13601 "inputs": {
13602 "name": "pikachu"
13603 },
13604 "output": {
13605 "height_dm": 4,
13606 "name": "pikachu",
13607 "number": 25,
13608 "types": [
13609 "electric"
13610 ],
13611 "weight_hg": 60
13612 },
13613 "say": "#25 Pikachu, electric type.",
13614 "status": "done",
13615 "trace": {
13616 "gaps": [],
13617 "steps": [
13618 {
13619 "depth": 0,
13620 "kind": "intent",
13621 "next": "fetch",
13622 "node": "intent",
13623 "observed": {
13624 "cleaned": "pokemon pikachu",
13625 "inputs": {
13626 "name": "pikachu"
13627 },
13628 "steps": [],
13629 "trigger": "pokemon {name:word}"
13630 },
13631 "summary": "'pokemon pikachu'; pokemon via 'pokemon {name:word}' (7 literal chars)"
13632 },
13633 {
13634 "depth": 0,
13635 "kind": "observe",
13636 "next": "status",
13637 "node": "fetch",
13638 "observed": {
13639 "dm": 4,
13640 "hg": 60,
13641 "id": 25,
13642 "name": "pikachu",
13643 "types": [
13644 "electric"
13645 ]
13646 },
13647 "summary": "GET https://pokeapi.co/api/v2/pokemon/pikachu -> 200; id=25, name=pikachu, types=['electric'], dm=4, hg=60"
13648 },
13649 {
13650 "chosen": "eq 200",
13651 "depth": 0,
13652 "kind": "decide",
13653 "next": "reply",
13654 "node": "status",
13655 "observed": 200,
13656 "summary": "state.resp.status = 200"
13657 },
13658 {
13659 "depth": 0,
13660 "kind": "done",
13661 "next": null,
13662 "node": "reply",
13663 "summary": "#25 Pikachu, electric type."
13664 }
13665 ],
13666 "tree": "pokemon"
13667 },
13668 "tree": "pokemon"
13669 },
13670 "inputs": {
13671 "name": "pikachu"
13672 },
13673 "intent": "pokemon pikachu",
13674 "tree": "pokemon"
13675 },
13676 {
13677 "expect": {
13678 "inputs": {
13679 "name": "charizard"
13680 },
13681 "output": {
13682 "height_dm": 17,
13683 "name": "charizard",
13684 "number": 6,
13685 "types": [
13686 "fire",
13687 "flying"
13688 ],
13689 "weight_hg": 905
13690 },
13691 "say": "#6 Charizard, fire, flying type.",
13692 "status": "done",
13693 "trace": {
13694 "gaps": [],
13695 "steps": [
13696 {
13697 "depth": 0,
13698 "kind": "intent",
13699 "next": "fetch",
13700 "node": "intent",
13701 "observed": {
13702 "cleaned": "what type is charizard",
13703 "inputs": {
13704 "name": "charizard"
13705 },
13706 "steps": [],
13707 "trigger": "what type is {name:word}"
13708 },
13709 "summary": "'what type is charizard'; pokemon via 'what type is {name:word}' (10 literal chars)"
13710 },
13711 {
13712 "depth": 0,
13713 "kind": "observe",
13714 "next": "status",
13715 "node": "fetch",
13716 "observed": {
13717 "dm": 17,
13718 "hg": 905,
13719 "id": 6,
13720 "name": "charizard",
13721 "types": [
13722 "fire",
13723 "flying"
13724 ]
13725 },
13726 "summary": "GET https://pokeapi.co/api/v2/pokemon/charizard -> 200; id=6, name=charizard, types=['fire', 'flying'], dm=17, hg=905"
13727 },
13728 {
13729 "chosen": "eq 200",
13730 "depth": 0,
13731 "kind": "decide",
13732 "next": "reply",
13733 "node": "status",
13734 "observed": 200,
13735 "summary": "state.resp.status = 200"
13736 },
13737 {
13738 "depth": 0,
13739 "kind": "done",
13740 "next": null,
13741 "node": "reply",
13742 "summary": "#6 Charizard, fire, flying type."
13743 }
13744 ],
13745 "tree": "pokemon"
13746 },
13747 "tree": "pokemon"
13748 },
13749 "inputs": {
13750 "name": "charizard"
13751 },
13752 "intent": "what type is charizard",
13753 "tree": "pokemon"
13754 },
13755 {
13756 "expect": {
13757 "inputs": {
13758 "name": "notapokemonqq"
13759 },
13760 "reason": "no-pokemon",
13761 "say": "PokéAPI knows no Pokémon called notapokemonqq.",
13762 "status": "failed",
13763 "trace": {
13764 "gaps": [],
13765 "steps": [
13766 {
13767 "depth": 0,
13768 "kind": "intent",
13769 "next": "fetch",
13770 "node": "intent",
13771 "observed": {
13772 "cleaned": "pokemon notapokemonqq",
13773 "inputs": {
13774 "name": "notapokemonqq"
13775 },
13776 "steps": [],
13777 "trigger": "pokemon {name:word}"
13778 },
13779 "summary": "'pokemon notapokemonqq'; pokemon via 'pokemon {name:word}' (7 literal chars)"
13780 },
13781 {
13782 "depth": 0,
13783 "kind": "observe",
13784 "next": "status",
13785 "node": "fetch",
13786 "observed": {
13787 "dm": null,
13788 "hg": null,
13789 "id": null,
13790 "name": null,
13791 "types": []
13792 },
13793 "summary": "GET https://pokeapi.co/api/v2/pokemon/notapokemonqq -> 404; id=None, name=None, types=[], dm=None, hg=None"
13794 },
13795 {
13796 "chosen": "eq 404",
13797 "depth": 0,
13798 "kind": "decide",
13799 "next": "no-pokemon",
13800 "node": "status",
13801 "observed": 404,
13802 "summary": "state.resp.status = 404"
13803 },
13804 {
13805 "depth": 0,
13806 "kind": "fail",
13807 "next": null,
13808 "node": "no-pokemon",
13809 "summary": "no-pokemon: PokéAPI knows no Pokémon called notapokemonqq."
13810 }
13811 ],
13812 "tree": "pokemon"
13813 },
13814 "tree": "pokemon"
13815 },
13816 "inputs": {
13817 "name": "notapokemonqq"
13818 },
13819 "intent": "pokemon notapokemonqq",
13820 "tree": "pokemon"
13821 },
13822 {
13823 "expect": {
13824 "inputs": {},
13825 "output": {
13826 "ip": "203.0.113.7"
13827 },
13828 "say": "Your public IP address is 203.0.113.7.",
13829 "status": "done",
13830 "trace": {
13831 "gaps": [],
13832 "steps": [
13833 {
13834 "depth": 0,
13835 "kind": "intent",
13836 "next": "fetch",
13837 "node": "intent",
13838 "observed": {
13839 "cleaned": "what is my ip",
13840 "inputs": {},
13841 "steps": [],
13842 "trigger": "what is my [public] ip [address]"
13843 },
13844 "summary": "'what is my ip'; my-ip via 'what is my [public] ip [address]' (23 literal chars)"
13845 },
13846 {
13847 "depth": 0,
13848 "kind": "observe",
13849 "next": "status",
13850 "node": "fetch",
13851 "observed": {
13852 "ip": "203.0.113.7"
13853 },
13854 "summary": "GET https://api.ipify.org?format=json -> 200; ip=203.0.113.7"
13855 },
13856 {
13857 "chosen": "eq 200",
13858 "depth": 0,
13859 "kind": "decide",
13860 "next": "has-ip",
13861 "node": "status",
13862 "observed": 200,
13863 "summary": "state.resp.status = 200"
13864 },
13865 {
13866 "chosen": "exists True",
13867 "depth": 0,
13868 "kind": "decide",
13869 "next": "reply",
13870 "node": "has-ip",
13871 "observed": "203.0.113.7",
13872 "summary": "state.ip = 203.0.113.7"
13873 },
13874 {
13875 "depth": 0,
13876 "kind": "done",
13877 "next": null,
13878 "node": "reply",
13879 "summary": "Your public IP address is 203.0.113.7."
13880 }
13881 ],
13882 "tree": "my-ip"
13883 },
13884 "tree": "my-ip"
13885 },
13886 "inputs": {},
13887 "intent": "what is my ip",
13888 "tree": "my-ip"
13889 },
13890 {
13891 "expect": {
13892 "inputs": {},
13893 "output": {
13894 "advice": "Take time once in a while to look up at the stars for at least 5 minutes, in order to comprehend your cosmic significance.",
13895 "id": 66
13896 },
13897 "say": "Take time once in a while to look up at the stars for at least 5 minutes, in order to comprehend your cosmic significance.",
13898 "status": "done",
13899 "trace": {
13900 "gaps": [],
13901 "steps": [
13902 {
13903 "depth": 0,
13904 "kind": "intent",
13905 "next": "fetch",
13906 "node": "intent",
13907 "observed": {
13908 "cleaned": "give me some advice",
13909 "inputs": {},
13910 "steps": [],
13911 "trigger": "give me [some] advice"
13912 },
13913 "summary": "'give me some advice'; advice via 'give me [some] advice' (16 literal chars)"
13914 },
13915 {
13916 "depth": 0,
13917 "kind": "observe",
13918 "next": "status",
13919 "node": "fetch",
13920 "observed": {
13921 "advice": "Take time once in a while to look up at the stars for at least 5 minutes, in order to comprehend your cosmic significance.",
13922 "id": 66
13923 },
13924 "summary": "GET https://api.adviceslip.com/advice -> 200; advice=Take time once in a while to look up at the stars for at least 5 minutes, in or…, id=66"
13925 },
13926 {
13927 "chosen": "eq 200",
13928 "depth": 0,
13929 "kind": "decide",
13930 "next": "has-advice",
13931 "node": "status",
13932 "observed": 200,
13933 "summary": "state.resp.status = 200"
13934 },
13935 {
13936 "chosen": "exists True",
13937 "depth": 0,
13938 "kind": "decide",
13939 "next": "reply",
13940 "node": "has-advice",
13941 "observed": "Take time once in a while to look up at the stars for at least 5 minutes, in order to comprehend your cosmic significance.",
13942 "summary": "state.advice = Take time once in a while to look up at the stars for at least 5 minutes, in or…"
13943 },
13944 {
13945 "depth": 0,
13946 "kind": "done",
13947 "next": null,
13948 "node": "reply",
13949 "summary": "Take time once in a while to look up at the stars for at least 5 minutes, in order to comprehend your cosmic significance."
13950 }
13951 ],
13952 "tree": "advice"
13953 },
13954 "tree": "advice"
13955 },
13956 "inputs": {},
13957 "intent": "give me some advice",
13958 "tree": "advice"
13959 },
13960 {
13961 "expect": {
13962 "inputs": {},
13963 "output": {
13964 "fact": "A cat has more bones than a human; humans have 206, and the cat - 230."
13965 },
13966 "say": "A cat has more bones than a human; humans have 206, and the cat - 230.",
13967 "status": "done",
13968 "trace": {
13969 "gaps": [],
13970 "steps": [
13971 {
13972 "depth": 0,
13973 "kind": "intent",
13974 "next": "fetch",
13975 "node": "intent",
13976 "observed": {
13977 "cleaned": "tell me a cat fact",
13978 "inputs": {},
13979 "steps": [],
13980 "trigger": "[tell me a] cat fact"
13981 },
13982 "summary": "'tell me a cat fact'; cat-fact via '[tell me a] cat fact' (14 literal chars)"
13983 },
13984 {
13985 "depth": 0,
13986 "kind": "observe",
13987 "next": "status",
13988 "node": "fetch",
13989 "observed": {
13990 "fact": "A cat has more bones than a human; humans have 206, and the cat - 230."
13991 },
13992 "summary": "GET https://catfact.ninja/fact -> 200; fact=A cat has more bones than a human; humans have 206, and the cat - 230."
13993 },
13994 {
13995 "chosen": "eq 200",
13996 "depth": 0,
13997 "kind": "decide",
13998 "next": "has-fact",
13999 "node": "status",
14000 "observed": 200,
14001 "summary": "state.resp.status = 200"
14002 },
14003 {
14004 "chosen": "exists True",
14005 "depth": 0,
14006 "kind": "decide",
14007 "next": "reply",
14008 "node": "has-fact",
14009 "observed": "A cat has more bones than a human; humans have 206, and the cat - 230.",
14010 "summary": "state.fact = A cat has more bones than a human; humans have 206, and the cat - 230."
14011 },
14012 {
14013 "depth": 0,
14014 "kind": "done",
14015 "next": null,
14016 "node": "reply",
14017 "summary": "A cat has more bones than a human; humans have 206, and the cat - 230."
14018 }
14019 ],
14020 "tree": "cat-fact"
14021 },
14022 "tree": "cat-fact"
14023 },
14024 "inputs": {},
14025 "intent": "tell me a cat fact",
14026 "tree": "cat-fact"
14027 },
14028 {
14029 "expect": {
14030 "inputs": {},
14031 "output": {
14032 "name": "Falcon 9 Block 5 | Starlink Group 15-18",
14033 "net_utc": "2026-10-30T04:02:00Z",
14034 "status": "Go for Launch",
14035 "upcoming": 474
14036 },
14037 "say": "Next launch: Falcon 9 Block 5 | Starlink Group 15-18, 2026-10-30T04:02:00Z UTC (Go for Launch). 474 launches are scheduled.",
14038 "status": "done",
14039 "trace": {
14040 "gaps": [],
14041 "steps": [
14042 {
14043 "depth": 0,
14044 "kind": "intent",
14045 "next": "fetch",
14046 "node": "intent",
14047 "observed": {
14048 "cleaned": "when is the next launch",
14049 "inputs": {},
14050 "steps": [],
14051 "trigger": "[when is the] next (launch|rocket launch)"
14052 },
14053 "summary": "'when is the next launch'; next-launch via '[when is the] next (launch|rocket launch)' (31 literal chars)"
14054 },
14055 {
14056 "depth": 0,
14057 "kind": "observe",
14058 "next": "status",
14059 "node": "fetch",
14060 "observed": {
14061 "count": 474,
14062 "name": "Falcon 9 Block 5 | Starlink Group 15-18",
14063 "net": "2026-10-30T04:02:00Z",
14064 "state": "Go for Launch"
14065 },
14066 "summary": "GET https://ll.thespacedevs.com/2.3.0/launches/upcoming/?limit=1&mode=list&hide_recent_previous=true -> 200; name=Falcon 9 Block 5 | Starlink Group 15-18, net=2026-10-30T04:02:00Z, state=Go for Launch, count=474"
14067 },
14068 {
14069 "chosen": "eq 200",
14070 "depth": 0,
14071 "kind": "decide",
14072 "next": "any",
14073 "node": "status",
14074 "observed": 200,
14075 "summary": "state.resp.status = 200"
14076 },
14077 {
14078 "chosen": "exists True",
14079 "depth": 0,
14080 "kind": "decide",
14081 "next": "reply",
14082 "node": "any",
14083 "observed": "Falcon 9 Block 5 | Starlink Group 15-18",
14084 "summary": "state.name = Falcon 9 Block 5 | Starlink Group 15-18"
14085 },
14086 {
14087 "depth": 0,
14088 "kind": "done",
14089 "next": null,
14090 "node": "reply",
14091 "summary": "Next launch: Falcon 9 Block 5 | Starlink Group 15-18, 2026-10-30T04:02:00Z UTC (Go for Launch). 474 launches are scheduled."
14092 }
14093 ],
14094 "tree": "next-launch"
14095 },
14096 "tree": "next-launch"
14097 },
14098 "inputs": {},
14099 "intent": "when is the next launch",
14100 "tree": "next-launch"
14101 },
14102 {
14103 "expect": {
14104 "inputs": {},
14105 "output": {
14106 "stories": [
14107 {
14108 "published": "2026-10-27T20:21:30Z",
14109 "site": "SpaceNews",
14110 "title": "Ukrainian startup, GomSpace test satellite for future communications network",
14111 "url": "https://spacenews.com/ukrainian-startup-gomspace-test-satellite-for-future-communications-network/"
14112 },
14113 {
14114 "published": "2026-10-27T19:27:22Z",
14115 "site": "NASA",
14116 "title": "NASA Invites Media to Crew Completion of Yearlong Mars Simulation",
14117 "url": "https://www.nasa.gov/news-release/nasa-invites-media-to-crew-completion-of-yearlong-mars-simulation/"
14118 },
14119 {
14120 "published": "2026-10-27T17:58:57Z",
14121 "site": "NASA",
14122 "title": "NASA’s Neo Surveyor Sunshade",
14123 "url": "https://www.nasa.gov/image-article/nasas-neo-surveyor-sunshade/"
14124 }
14125 ]
14126 },
14127 "say": "Latest in space: Ukrainian startup, GomSpace test satellite for future communications network (SpaceNews) https://spacenews.com/ukrainian-startup-gomspace-test-satellite-for-future-communications-network/",
14128 "status": "done",
14129 "trace": {
14130 "gaps": [],
14131 "steps": [
14132 {
14133 "depth": 0,
14134 "kind": "intent",
14135 "next": "fetch",
14136 "node": "intent",
14137 "observed": {
14138 "cleaned": "space news",
14139 "inputs": {},
14140 "steps": [],
14141 "trigger": "[latest] (space|spaceflight) news"
14142 },
14143 "summary": "'space news'; space-news via '[latest] (space|spaceflight) news' (26 literal chars)"
14144 },
14145 {
14146 "depth": 0,
14147 "kind": "observe",
14148 "next": "status",
14149 "node": "fetch",
14150 "observed": {
14151 "articles": [
14152 {
14153 "news_site": "SpaceNews",
14154 "published_at": "2026-10-27T20:21:30Z",
14155 "title": "Ukrainian startup, GomSpace test satellite for future communications network",
14156 "url": "https://spacenews.com/ukrainian-startup-gomspace-test-satellite-for-future-communications-network/"
14157 },
14158 {
14159 "news_site": "NASA",
14160 "published_at": "2026-10-27T19:27:22Z",
14161 "title": "NASA Invites Media to Crew Completion of Yearlong Mars Simulation",
14162 "url": "https://www.nasa.gov/news-release/nasa-invites-media-to-crew-completion-of-yearlong-mars-simulation/"
14163 },
14164 {
14165 "news_site": "NASA",
14166 "published_at": "2026-10-27T17:58:57Z",
14167 "title": "NASA’s Neo Surveyor Sunshade",
14168 "url": "https://www.nasa.gov/image-article/nasas-neo-surveyor-sunshade/"
14169 }
14170 ],
14171 "site": "SpaceNews",
14172 "title": "Ukrainian startup, GomSpace test satellite for future communications network",
14173 "url": "https://spacenews.com/ukrainian-startup-gomspace-test-satellite-for-future-communications-network/"
14174 },
14175 "summary": "GET https://api.spaceflightnewsapi.net/v4/articles/?limit=3 -> 200; articles=[{'title': 'Ukrainian startup, GomSpace test satellite for future communication…, title=Ukrainian startup, GomSpace test satellite for future communications network, site=SpaceNews, url=https://spacenews.com/ukrainian-startup-gomspace-test-satellite-for-future-comm…"
14176 },
14177 {
14178 "chosen": "eq 200",
14179 "depth": 0,
14180 "kind": "decide",
14181 "next": "any",
14182 "node": "status",
14183 "observed": 200,
14184 "summary": "state.resp.status = 200"
14185 },
14186 {
14187 "chosen": "else",
14188 "depth": 0,
14189 "kind": "decide",
14190 "next": "each",
14191 "node": "any",
14192 "observed": [
14193 {
14194 "news_site": "SpaceNews",
14195 "published_at": "2026-10-27T20:21:30Z",
14196 "title": "Ukrainian startup, GomSpace test satellite for future communications network",
14197 "url": "https://spacenews.com/ukrainian-startup-gomspace-test-satellite-for-future-communications-network/"
14198 },
14199 {
14200 "news_site": "NASA",
14201 "published_at": "2026-10-27T19:27:22Z",
14202 "title": "NASA Invites Media to Crew Completion of Yearlong Mars Simulation",
14203 "url": "https://www.nasa.gov/news-release/nasa-invites-media-to-crew-completion-of-yearlong-mars-simulation/"
14204 },
14205 {
14206 "news_site": "NASA",
14207 "published_at": "2026-10-27T17:58:57Z",
14208 "title": "NASA’s Neo Surveyor Sunshade",
14209 "url": "https://www.nasa.gov/image-article/nasas-neo-surveyor-sunshade/"
14210 }
14211 ],
14212 "summary": "state.articles = [{'title': 'Ukrainian startup, GomSpace test satellite for future communication…"
14213 },
14214 {
14215 "depth": 1,
14216 "kind": "done",
14217 "next": null,
14218 "node": "one",
14219 "summary": "done"
14220 },
14221 {
14222 "depth": 1,
14223 "kind": "done",
14224 "next": null,
14225 "node": "one",
14226 "summary": "done"
14227 },
14228 {
14229 "depth": 1,
14230 "kind": "done",
14231 "next": null,
14232 "node": "one",
14233 "summary": "done"
14234 },
14235 {
14236 "depth": 0,
14237 "kind": "each",
14238 "next": "reply",
14239 "node": "each",
14240 "summary": "3 item(s)"
14241 },
14242 {
14243 "depth": 0,
14244 "kind": "done",
14245 "next": null,
14246 "node": "reply",
14247 "summary": "Latest in space: Ukrainian startup, GomSpace test satellite for future communications network (SpaceNews) https://spacenews.com/ukrainian-startup-gomspace-test-satellite-for-future-communications-network/"
14248 }
14249 ],
14250 "tree": "space-news"
14251 },
14252 "tree": "space-news"
14253 },
14254 "inputs": {},
14255 "intent": "space news",
14256 "tree": "space-news"
14257 },
14258 {
14259 "expect": {
14260 "inputs": {},
14261 "output": {
14262 "date": "2026-10-28",
14263 "explanation": "Sometimes it's night on the ground but day in the air.",
14264 "media_type": "image",
14265 "page": "https://apod.nasa.gov/apod/astropix.html",
14266 "title": "Day and Night over the Clouds",
14267 "url": "https://apod.nasa.gov/apod/image/2610/example_1024.jpg"
14268 },
14269 "say": "Astronomy Picture of the Day, 2026-10-28: Day and Night over the Clouds. https://apod.nasa.gov/apod/image/2610/example_1024.jpg",
14270 "status": "done",
14271 "trace": {
14272 "gaps": [],
14273 "steps": [
14274 {
14275 "depth": 0,
14276 "kind": "intent",
14277 "next": "fetch",
14278 "node": "intent",
14279 "observed": {
14280 "cleaned": "astronomy picture of the day",
14281 "inputs": {},
14282 "steps": [],
14283 "trigger": "astronomy picture of the day"
14284 },
14285 "summary": "'astronomy picture of the day'; apod via 'astronomy picture of the day' (24 literal chars)"
14286 },
14287 {
14288 "depth": 0,
14289 "kind": "observe",
14290 "next": "status",
14291 "node": "fetch",
14292 "observed": {
14293 "about": "Sometimes it's night on the ground but day in the air.",
14294 "day": "2026-10-28",
14295 "kind": "image",
14296 "media": "https://apod.nasa.gov/apod/image/2610/example_1024.jpg",
14297 "title": "Day and Night over the Clouds"
14298 },
14299 "summary": "GET https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY -> 200; title=Day and Night over the Clouds, day=2026-10-28, media=https://apod.nasa.gov/apod/image/2610/example_1024.jpg, kind=image, about=Sometimes it's night on the ground but day in the air."
14300 },
14301 {
14302 "chosen": "eq 200",
14303 "depth": 0,
14304 "kind": "decide",
14305 "next": "reply",
14306 "node": "status",
14307 "observed": 200,
14308 "summary": "state.resp.status = 200"
14309 },
14310 {
14311 "depth": 0,
14312 "kind": "done",
14313 "next": null,
14314 "node": "reply",
14315 "summary": "Astronomy Picture of the Day, 2026-10-28: Day and Night over the Clouds. https://apod.nasa.gov/apod/image/2610/example_1024.jpg"
14316 }
14317 ],
14318 "tree": "apod"
14319 },
14320 "tree": "apod"
14321 },
14322 "inputs": {},
14323 "intent": "astronomy picture of the day",
14324 "tree": "apod"
14325 }
14326 ],
14327 "trees": {
14328 "advice": {
14329 "description": "A random piece of advice, from the Advice Slip API.",
14330 "hosts": [
14331 "api.adviceslip.com"
14332 ],
14333 "license": "MIT",
14334 "name": "advice",
14335 "namespace": "pyrtan",
14336 "nodes": {
14337 "fetch": {
14338 "extract": [
14339 {
14340 "into": "advice",
14341 "json": "json.slip.advice"
14342 },
14343 {
14344 "into": "id",
14345 "json": "json.slip.id"
14346 }
14347 ],
14348 "http": {
14349 "method": "GET",
14350 "url": "https://api.adviceslip.com/advice"
14351 },
14352 "into": "resp",
14353 "kind": "observe",
14354 "next": "status"
14355 },
14356 "has-advice": {
14357 "branches": [
14358 {
14359 "then": "reply",
14360 "when": {
14361 "exists": true
14362 }
14363 },
14364 {
14365 "else": "unexpected"
14366 }
14367 ],
14368 "kind": "decide",
14369 "on": "advice"
14370 },
14371 "reply": {
14372 "kind": "done",
14373 "output": {
14374 "advice": "{{ state.advice }}",
14375 "id": "{{ state.id }}"
14376 },
14377 "say": "{{ state.advice }}"
14378 },
14379 "status": {
14380 "branches": [
14381 {
14382 "then": "has-advice",
14383 "when": {
14384 "eq": 200
14385 }
14386 },
14387 {
14388 "else": "unexpected"
14389 }
14390 ],
14391 "kind": "decide",
14392 "on": "resp.status"
14393 },
14394 "unexpected": {
14395 "kind": "fail",
14396 "reason": "unexpected-status",
14397 "say": "The Advice Slip API answered {{ state.resp.status }}."
14398 }
14399 },
14400 "start": "fetch",
14401 "tags": [
14402 "fun",
14403 "read-only"
14404 ],
14405 "triggers": [
14406 {
14407 "pattern": "give me [some] advice"
14408 },
14409 {
14410 "pattern": "[any] advice"
14411 }
14412 ],
14413 "version": 1
14414 },
14415 "air-quality": {
14416 "description": "Current air quality for a place: the European AQI, its band, and PM2.5 and PM10, from Open-Meteo.",
14417 "hosts": [
14418 "geocoding-api.open-meteo.com",
14419 "air-quality-api.open-meteo.com"
14420 ],
14421 "inputs": {
14422 "place": {
14423 "description": "A town or city name",
14424 "type": "str"
14425 }
14426 },
14427 "license": "MIT",
14428 "name": "air-quality",
14429 "namespace": "pyrtan",
14430 "nodes": {
14431 "air": {
14432 "extract": [
14433 {
14434 "into": "aqi",
14435 "json": "json.current.european_aqi"
14436 },
14437 {
14438 "into": "pm25",
14439 "json": "json.current.pm2_5"
14440 },
14441 {
14442 "into": "pm10",
14443 "json": "json.current.pm10"
14444 }
14445 ],
14446 "http": {
14447 "method": "GET",
14448 "url": "https://air-quality-api.open-meteo.com/v1/air-quality?latitude={{ state.lat }}&longitude={{ state.lon }}&current=european_aqi,pm2_5,pm10"
14449 },
14450 "into": "aq",
14451 "kind": "observe",
14452 "next": "band"
14453 },
14454 "band": {
14455 "branches": [
14456 {
14457 "then": "good",
14458 "when": {
14459 "lte": 20,
14460 "type": "number"
14461 }
14462 },
14463 {
14464 "then": "fair",
14465 "when": {
14466 "lte": 40
14467 }
14468 },
14469 {
14470 "then": "moderate",
14471 "when": {
14472 "lte": 60
14473 }
14474 },
14475 {
14476 "then": "poor",
14477 "when": {
14478 "lte": 80
14479 }
14480 },
14481 {
14482 "then": "very-poor",
14483 "when": {
14484 "lte": 100
14485 }
14486 },
14487 {
14488 "then": "extremely-poor",
14489 "when": {
14490 "gt": 100
14491 }
14492 },
14493 {
14494 "else": "no-reading"
14495 }
14496 ],
14497 "kind": "decide",
14498 "on": "aqi"
14499 },
14500 "extremely-poor": {
14501 "kind": "set",
14502 "next": "reply",
14503 "set": {
14504 "band": "extremely poor"
14505 }
14506 },
14507 "fair": {
14508 "kind": "set",
14509 "next": "reply",
14510 "set": {
14511 "band": "fair"
14512 }
14513 },
14514 "found": {
14515 "branches": [
14516 {
14517 "then": "air",
14518 "when": {
14519 "exists": true
14520 }
14521 },
14522 {
14523 "else": "unknown-place"
14524 }
14525 ],
14526 "kind": "decide",
14527 "on": "hit"
14528 },
14529 "geocode": {
14530 "extract": [
14531 {
14532 "into": "hit",
14533 "json": "json.results[0]"
14534 },
14535 {
14536 "into": "lat",
14537 "json": "json.results[0].latitude"
14538 },
14539 {
14540 "into": "lon",
14541 "json": "json.results[0].longitude"
14542 },
14543 {
14544 "into": "name",
14545 "json": "json.results[0].name"
14546 },
14547 {
14548 "into": "country",
14549 "json": "json.results[0].country"
14550 }
14551 ],
14552 "http": {
14553 "method": "GET",
14554 "url": "https://geocoding-api.open-meteo.com/v1/search?name={{ inputs.place | url }}&count=1&language=en&format=json"
14555 },
14556 "into": "geo",
14557 "kind": "observe",
14558 "next": "found"
14559 },
14560 "good": {
14561 "kind": "set",
14562 "next": "reply",
14563 "set": {
14564 "band": "good"
14565 }
14566 },
14567 "moderate": {
14568 "kind": "set",
14569 "next": "reply",
14570 "set": {
14571 "band": "moderate"
14572 }
14573 },
14574 "no-reading": {
14575 "kind": "fail",
14576 "reason": "no-reading",
14577 "say": "Open-Meteo found {{ state.name }} but gave no air quality reading (status {{ state.aq.status }})."
14578 },
14579 "poor": {
14580 "kind": "set",
14581 "next": "reply",
14582 "set": {
14583 "band": "poor"
14584 }
14585 },
14586 "reply": {
14587 "kind": "done",
14588 "output": {
14589 "aqi": "{{ state.aqi }}",
14590 "band": "{{ state.band }}",
14591 "country": "{{ state.country }}",
14592 "place": "{{ state.name }}",
14593 "pm10": "{{ state.pm10 }}",
14594 "pm2_5": "{{ state.pm25 }}"
14595 },
14596 "say": "{{ state.name }}, {{ state.country }}: air quality {{ state.band }} (European AQI {{ state.aqi }}), PM2.5 {{ state.pm25 }} µg/m³, PM10 {{ state.pm10 }} µg/m³."
14597 },
14598 "unknown-place": {
14599 "kind": "fail",
14600 "reason": "unknown-place",
14601 "say": "I could not find a place called {{ inputs.place }}."
14602 },
14603 "very-poor": {
14604 "kind": "set",
14605 "next": "reply",
14606 "set": {
14607 "band": "very poor"
14608 }
14609 }
14610 },
14611 "start": "geocode",
14612 "tags": [
14613 "weather",
14614 "health",
14615 "read-only"
14616 ],
14617 "triggers": [
14618 {
14619 "pattern": "[what's the] air quality (in|for) {place}"
14620 },
14621 {
14622 "pattern": "how is the air in {place}"
14623 },
14624 {
14625 "pattern": "(aqi|pollution) in {place}"
14626 }
14627 ],
14628 "version": 1
14629 },
14630 "apod": {
14631 "description": "NASA's Astronomy Picture of the Day: its title, and the picture or video.",
14632 "hosts": [
14633 "api.nasa.gov"
14634 ],
14635 "license": "MIT",
14636 "name": "apod",
14637 "namespace": "pyrtan",
14638 "nodes": {
14639 "fetch": {
14640 "extract": [
14641 {
14642 "into": "title",
14643 "json": "json.title"
14644 },
14645 {
14646 "into": "day",
14647 "json": "json.date"
14648 },
14649 {
14650 "into": "media",
14651 "json": "json.url"
14652 },
14653 {
14654 "into": "kind",
14655 "json": "json.media_type"
14656 },
14657 {
14658 "into": "about",
14659 "json": "json.explanation"
14660 }
14661 ],
14662 "http": {
14663 "method": "GET",
14664 "url": "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY"
14665 },
14666 "into": "resp",
14667 "kind": "observe",
14668 "next": "status"
14669 },
14670 "rate-limited": {
14671 "kind": "fail",
14672 "reason": "rate-limited",
14673 "say": "NASA's demo key has used up its hour from this address; try again later, or see https://apod.nasa.gov/apod/astropix.html"
14674 },
14675 "reply": {
14676 "kind": "done",
14677 "output": {
14678 "date": "{{ state.day }}",
14679 "explanation": "{{ state.about }}",
14680 "media_type": "{{ state.kind }}",
14681 "page": "https://apod.nasa.gov/apod/astropix.html",
14682 "title": "{{ state.title }}",
14683 "url": "{{ state.media }}"
14684 },
14685 "say": "Astronomy Picture of the Day, {{ state.day }}: {{ state.title }}. {{ state.media }}"
14686 },
14687 "status": {
14688 "branches": [
14689 {
14690 "then": "reply",
14691 "when": {
14692 "eq": 200
14693 }
14694 },
14695 {
14696 "then": "rate-limited",
14697 "when": {
14698 "eq": 429
14699 }
14700 },
14701 {
14702 "else": "unexpected"
14703 }
14704 ],
14705 "kind": "decide",
14706 "on": "resp.status"
14707 },
14708 "unexpected": {
14709 "kind": "fail",
14710 "reason": "unexpected-status",
14711 "say": "NASA's API answered {{ state.resp.status }}."
14712 }
14713 },
14714 "start": "fetch",
14715 "tags": [
14716 "space",
14717 "science",
14718 "read-only"
14719 ],
14720 "triggers": [
14721 {
14722 "pattern": "astronomy picture of the day"
14723 },
14724 {
14725 "pattern": "[nasa] (apod|space picture) [of the day|today]"
14726 }
14727 ],
14728 "version": 1
14729 },
14730 "books": {
14731 "description": "A book from Open Library: the best match for a title or an author, who wrote it, and when it first came out.",
14732 "hosts": [
14733 "openlibrary.org"
14734 ],
14735 "inputs": {
14736 "title": {
14737 "description": "A title or an author, as in: The Left Hand of Darkness",
14738 "type": "str"
14739 }
14740 },
14741 "license": "MIT",
14742 "name": "books",
14743 "namespace": "pyrtan",
14744 "nodes": {
14745 "any": {
14746 "branches": [
14747 {
14748 "then": "reply",
14749 "when": {
14750 "exists": true
14751 }
14752 },
14753 {
14754 "else": "no-book"
14755 }
14756 ],
14757 "kind": "decide",
14758 "on": "book"
14759 },
14760 "no-book": {
14761 "kind": "fail",
14762 "reason": "no-book",
14763 "say": "Open Library has nothing matching {{ inputs.title }}."
14764 },
14765 "reply": {
14766 "kind": "done",
14767 "output": {
14768 "author": "{{ state.author }}",
14769 "first_published": "{{ state.year }}",
14770 "matches": "{{ state.found }}",
14771 "title": "{{ state.book }}",
14772 "url": "https://openlibrary.org{{ state.key }}"
14773 },
14774 "say": "{{ state.book }}, by {{ state.author }} ({{ state.year }}). https://openlibrary.org{{ state.key }}"
14775 },
14776 "search": {
14777 "extract": [
14778 {
14779 "into": "found",
14780 "json": "json.numFound"
14781 },
14782 {
14783 "into": "book",
14784 "json": "json.docs[0].title"
14785 },
14786 {
14787 "into": "author",
14788 "json": "json.docs[0].author_name[0]"
14789 },
14790 {
14791 "into": "year",
14792 "json": "json.docs[0].first_publish_year"
14793 },
14794 {
14795 "into": "key",
14796 "json": "json.docs[0].key"
14797 }
14798 ],
14799 "http": {
14800 "method": "GET",
14801 "url": "https://openlibrary.org/search.json?q={{ inputs.title | url }}&limit=1&fields=title,author_name,first_publish_year,key"
14802 },
14803 "into": "resp",
14804 "kind": "observe",
14805 "next": "status"
14806 },
14807 "status": {
14808 "branches": [
14809 {
14810 "then": "any",
14811 "when": {
14812 "eq": 200
14813 }
14814 },
14815 {
14816 "else": "unexpected"
14817 }
14818 ],
14819 "kind": "decide",
14820 "on": "resp.status"
14821 },
14822 "unexpected": {
14823 "kind": "fail",
14824 "reason": "unexpected-status",
14825 "say": "Open Library answered {{ state.resp.status }}."
14826 }
14827 },
14828 "start": "search",
14829 "tags": [
14830 "reference",
14831 "books",
14832 "read-only"
14833 ],
14834 "triggers": [
14835 {
14836 "pattern": "who wrote {title}"
14837 },
14838 {
14839 "pattern": "find the book {title}"
14840 },
14841 {
14842 "pattern": "books by {title}"
14843 }
14844 ],
14845 "version": 1
14846 },
14847 "cat-fact": {
14848 "description": "A random fact about cats, from catfact.ninja.",
14849 "hosts": [
14850 "catfact.ninja"
14851 ],
14852 "license": "MIT",
14853 "name": "cat-fact",
14854 "namespace": "pyrtan",
14855 "nodes": {
14856 "fetch": {
14857 "extract": [
14858 {
14859 "into": "fact",
14860 "json": "json.fact"
14861 }
14862 ],
14863 "http": {
14864 "method": "GET",
14865 "url": "https://catfact.ninja/fact"
14866 },
14867 "into": "resp",
14868 "kind": "observe",
14869 "next": "status"
14870 },
14871 "has-fact": {
14872 "branches": [
14873 {
14874 "then": "reply",
14875 "when": {
14876 "exists": true
14877 }
14878 },
14879 {
14880 "else": "unexpected"
14881 }
14882 ],
14883 "kind": "decide",
14884 "on": "fact"
14885 },
14886 "reply": {
14887 "kind": "done",
14888 "output": {
14889 "fact": "{{ state.fact }}"
14890 },
14891 "say": "{{ state.fact }}"
14892 },
14893 "status": {
14894 "branches": [
14895 {
14896 "then": "has-fact",
14897 "when": {
14898 "eq": 200
14899 }
14900 },
14901 {
14902 "else": "unexpected"
14903 }
14904 ],
14905 "kind": "decide",
14906 "on": "resp.status"
14907 },
14908 "unexpected": {
14909 "kind": "fail",
14910 "reason": "unexpected-status",
14911 "say": "catfact.ninja answered {{ state.resp.status }}."
14912 }
14913 },
14914 "start": "fetch",
14915 "tags": [
14916 "fun",
14917 "animals",
14918 "read-only"
14919 ],
14920 "triggers": [
14921 {
14922 "pattern": "[tell me a] cat fact"
14923 },
14924 {
14925 "pattern": "tell me something about cats"
14926 }
14927 ],
14928 "version": 1
14929 },
14930 "dad-joke": {
14931 "description": "A random dad joke, from icanhazdadjoke.com.",
14932 "hosts": [
14933 "icanhazdadjoke.com"
14934 ],
14935 "license": "MIT",
14936 "name": "dad-joke",
14937 "namespace": "pyrtan",
14938 "nodes": {
14939 "fetch": {
14940 "extract": [
14941 {
14942 "into": "joke",
14943 "json": "json.joke"
14944 },
14945 {
14946 "into": "id",
14947 "json": "json.id"
14948 }
14949 ],
14950 "http": {
14951 "headers": {
14952 "Accept": "application/json"
14953 },
14954 "method": "GET",
14955 "url": "https://icanhazdadjoke.com/"
14956 },
14957 "into": "resp",
14958 "kind": "observe",
14959 "next": "status"
14960 },
14961 "has-joke": {
14962 "branches": [
14963 {
14964 "then": "reply",
14965 "when": {
14966 "exists": true
14967 }
14968 },
14969 {
14970 "else": "unexpected"
14971 }
14972 ],
14973 "kind": "decide",
14974 "on": "joke"
14975 },
14976 "reply": {
14977 "kind": "done",
14978 "output": {
14979 "joke": "{{ state.joke }}",
14980 "url": "https://icanhazdadjoke.com/j/{{ state.id }}"
14981 },
14982 "say": "{{ state.joke }}"
14983 },
14984 "status": {
14985 "branches": [
14986 {
14987 "then": "has-joke",
14988 "when": {
14989 "eq": 200
14990 }
14991 },
14992 {
14993 "else": "unexpected"
14994 }
14995 ],
14996 "kind": "decide",
14997 "on": "resp.status"
14998 },
14999 "unexpected": {
15000 "kind": "fail",
15001 "reason": "unexpected-status",
15002 "say": "icanhazdadjoke.com answered {{ state.resp.status }}."
15003 }
15004 },
15005 "start": "fetch",
15006 "tags": [
15007 "fun",
15008 "read-only"
15009 ],
15010 "triggers": [
15011 {
15012 "pattern": "tell me a [dad] joke"
15013 },
15014 {
15015 "pattern": "[a] dad joke"
15016 },
15017 {
15018 "pattern": "make me laugh"
15019 }
15020 ],
15021 "version": 1
15022 },
15023 "define": {
15024 "description": "What an English word means: its first definition, part of speech and pronunciation.",
15025 "hosts": [
15026 "api.dictionaryapi.dev"
15027 ],
15028 "inputs": {
15029 "word": {
15030 "description": "One English word",
15031 "pattern": "^[A-Za-z][A-Za-z'\\-]*$",
15032 "type": "str"
15033 }
15034 },
15035 "license": "MIT",
15036 "name": "define",
15037 "namespace": "pyrtan",
15038 "nodes": {
15039 "fetch": {
15040 "extract": [
15041 {
15042 "into": "word",
15043 "json": "json[0].word"
15044 },
15045 {
15046 "into": "phonetic",
15047 "json": "json[0].phonetic"
15048 },
15049 {
15050 "into": "part",
15051 "json": "json[0].meanings[0].partOfSpeech"
15052 },
15053 {
15054 "into": "definition",
15055 "json": "json[0].meanings[0].definitions[0].definition"
15056 }
15057 ],
15058 "http": {
15059 "method": "GET",
15060 "url": "https://api.dictionaryapi.dev/api/v2/entries/en/{{ inputs.word | lower | url }}"
15061 },
15062 "into": "resp",
15063 "kind": "observe",
15064 "next": "status"
15065 },
15066 "has-definition": {
15067 "branches": [
15068 {
15069 "then": "reply",
15070 "when": {
15071 "exists": true
15072 }
15073 },
15074 {
15075 "else": "unknown-word"
15076 }
15077 ],
15078 "kind": "decide",
15079 "on": "definition"
15080 },
15081 "reply": {
15082 "kind": "done",
15083 "output": {
15084 "definition": "{{ state.definition }}",
15085 "part_of_speech": "{{ state.part }}",
15086 "phonetic": "{{ state.phonetic }}",
15087 "word": "{{ state.word }}"
15088 },
15089 "say": "{{ state.word }} ({{ state.part }}): {{ state.definition }}"
15090 },
15091 "status": {
15092 "branches": [
15093 {
15094 "then": "has-definition",
15095 "when": {
15096 "eq": 200
15097 }
15098 },
15099 {
15100 "then": "unknown-word",
15101 "when": {
15102 "eq": 404
15103 }
15104 },
15105 {
15106 "else": "unexpected"
15107 }
15108 ],
15109 "kind": "decide",
15110 "on": "resp.status"
15111 },
15112 "unexpected": {
15113 "kind": "fail",
15114 "reason": "unexpected-status",
15115 "say": "The dictionary answered {{ state.resp.status }}."
15116 },
15117 "unknown-word": {
15118 "kind": "fail",
15119 "reason": "unknown-word",
15120 "say": "The dictionary has no entry for {{ inputs.word }}."
15121 }
15122 },
15123 "start": "fetch",
15124 "tags": [
15125 "reference",
15126 "language",
15127 "read-only"
15128 ],
15129 "triggers": [
15130 {
15131 "pattern": "define {word:word}"
15132 },
15133 {
15134 "pattern": "what does {word:word} mean"
15135 },
15136 {
15137 "pattern": "[the] (meaning|definition) of {word:word}"
15138 }
15139 ],
15140 "version": 1
15141 },
15142 "disk-space": {
15143 "commands": [
15144 "df"
15145 ],
15146 "description": "How full a filesystem is, from df on the box this tree runs on.",
15147 "inputs": {
15148 "mount": {
15149 "default": "/",
15150 "description": "A mount point, or any path on the filesystem to measure",
15151 "type": "str"
15152 }
15153 },
15154 "license": "MIT",
15155 "name": "disk-space",
15156 "namespace": "pyrtan",
15157 "nodes": {
15158 "df": {
15159 "extract": [
15160 {
15161 "group": 1,
15162 "into": "percent",
15163 "regex": "(\\d+)%"
15164 },
15165 {
15166 "group": 1,
15167 "into": "mounted",
15168 "regex": "%\\s+(\\S+)\\s*$"
15169 }
15170 ],
15171 "into": "df",
15172 "kind": "observe",
15173 "next": "ran",
15174 "shell": {
15175 "args": [
15176 "-P",
15177 "{{ inputs.mount }}"
15178 ],
15179 "command": "df"
15180 }
15181 },
15182 "failed": {
15183 "kind": "fail",
15184 "reason": "df-failed",
15185 "say": "df could not measure {{ inputs.mount }}: {{ state.df.stderr | strip }}"
15186 },
15187 "fine": {
15188 "kind": "done",
15189 "output": {
15190 "mounted": "{{ state.mounted }}",
15191 "percent": "{{ state.pct }}"
15192 },
15193 "say": "{{ state.mounted }} is {{ state.pct }}% full."
15194 },
15195 "full": {
15196 "kind": "fail",
15197 "reason": "disk-full",
15198 "say": "{{ state.mounted }} is {{ state.pct }}% full."
15199 },
15200 "level": {
15201 "branches": [
15202 {
15203 "then": "fine",
15204 "when": {
15205 "lt": 80
15206 }
15207 },
15208 {
15209 "then": "tight",
15210 "when": {
15211 "lt": 95
15212 }
15213 },
15214 {
15215 "else": "full"
15216 }
15217 ],
15218 "kind": "decide",
15219 "on": "pct"
15220 },
15221 "number": {
15222 "kind": "set",
15223 "next": "level",
15224 "set": {
15225 "pct": "{{ state.percent | int }}"
15226 }
15227 },
15228 "ran": {
15229 "branches": [
15230 {
15231 "then": "number",
15232 "when": {
15233 "eq": true
15234 }
15235 },
15236 {
15237 "else": "failed"
15238 }
15239 ],
15240 "kind": "decide",
15241 "on": "df.ok"
15242 },
15243 "tight": {
15244 "kind": "done",
15245 "output": {
15246 "mounted": "{{ state.mounted }}",
15247 "percent": "{{ state.pct }}"
15248 },
15249 "say": "{{ state.mounted }} is {{ state.pct }}% full; make room soon."
15250 }
15251 },
15252 "start": "df",
15253 "tags": [
15254 "ops",
15255 "box",
15256 "read-only"
15257 ],
15258 "triggers": [
15259 {
15260 "pattern": "[how] full is the disk"
15261 },
15262 {
15263 "pattern": "disk space"
15264 },
15265 {
15266 "pattern": "disk space on {mount:word}"
15267 }
15268 ],
15269 "version": 2
15270 },
15271 "earthquakes": {
15272 "description": "Magnitude 4.5+ earthquakes worldwide in the past day, from the USGS: how many, and the latest.",
15273 "hosts": [
15274 "earthquake.usgs.gov"
15275 ],
15276 "license": "MIT",
15277 "name": "earthquakes",
15278 "namespace": "pyrtan",
15279 "nodes": {
15280 "any": {
15281 "branches": [
15282 {
15283 "then": "none",
15284 "when": {
15285 "eq": 0
15286 }
15287 },
15288 {
15289 "then": "reply",
15290 "when": {
15291 "gt": 0
15292 }
15293 },
15294 {
15295 "else": "unexpected"
15296 }
15297 ],
15298 "kind": "decide",
15299 "on": "count"
15300 },
15301 "fetch": {
15302 "extract": [
15303 {
15304 "into": "count",
15305 "json": "json.metadata.count"
15306 },
15307 {
15308 "into": "mag",
15309 "json": "json.features[0].properties.mag"
15310 },
15311 {
15312 "into": "place",
15313 "json": "json.features[0].properties.place"
15314 },
15315 {
15316 "into": "tsunami",
15317 "json": "json.features[0].properties.tsunami"
15318 },
15319 {
15320 "into": "url",
15321 "json": "json.features[0].properties.url"
15322 }
15323 ],
15324 "http": {
15325 "method": "GET",
15326 "url": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
15327 },
15328 "into": "resp",
15329 "kind": "observe",
15330 "next": "status"
15331 },
15332 "none": {
15333 "kind": "done",
15334 "output": {
15335 "count": 0
15336 },
15337 "say": "No earthquakes of magnitude 4.5+ anywhere in the past day."
15338 },
15339 "reply": {
15340 "kind": "done",
15341 "output": {
15342 "count": "{{ state.count }}",
15343 "latest": {
15344 "magnitude": "{{ state.mag }}",
15345 "place": "{{ state.place }}",
15346 "tsunami": "{{ state.tsunami }}",
15347 "url": "{{ state.url }}"
15348 }
15349 },
15350 "say": "{{ state.count }} earthquake(s) of magnitude 4.5+ in the past day. The latest: M{{ state.mag }}, {{ state.place }}. {{ state.url }}"
15351 },
15352 "status": {
15353 "branches": [
15354 {
15355 "then": "any",
15356 "when": {
15357 "eq": 200
15358 }
15359 },
15360 {
15361 "else": "unexpected"
15362 }
15363 ],
15364 "kind": "decide",
15365 "on": "resp.status"
15366 },
15367 "unexpected": {
15368 "kind": "fail",
15369 "reason": "unexpected-status",
15370 "say": "The USGS feed answered {{ state.resp.status }}."
15371 }
15372 },
15373 "start": "fetch",
15374 "tags": [
15375 "science",
15376 "read-only"
15377 ],
15378 "triggers": [
15379 {
15380 "pattern": "[any] [recent] earthquakes [today]"
15381 },
15382 {
15383 "pattern": "[was there an] earthquake today"
15384 },
15385 {
15386 "pattern": "latest earthquake"
15387 }
15388 ],
15389 "version": 1
15390 },
15391 "elevation": {
15392 "description": "How high a place is above sea level, from Open-Meteo's geocoder.",
15393 "hosts": [
15394 "geocoding-api.open-meteo.com"
15395 ],
15396 "inputs": {
15397 "place": {
15398 "description": "A town or city name",
15399 "type": "str"
15400 }
15401 },
15402 "license": "MIT",
15403 "name": "elevation",
15404 "namespace": "pyrtan",
15405 "nodes": {
15406 "found": {
15407 "branches": [
15408 {
15409 "then": "has-height",
15410 "when": {
15411 "exists": true
15412 }
15413 },
15414 {
15415 "else": "unknown-place"
15416 }
15417 ],
15418 "kind": "decide",
15419 "on": "hit"
15420 },
15421 "geocode": {
15422 "extract": [
15423 {
15424 "into": "hit",
15425 "json": "json.results[0]"
15426 },
15427 {
15428 "into": "lat",
15429 "json": "json.results[0].latitude"
15430 },
15431 {
15432 "into": "lon",
15433 "json": "json.results[0].longitude"
15434 },
15435 {
15436 "into": "name",
15437 "json": "json.results[0].name"
15438 },
15439 {
15440 "into": "country",
15441 "json": "json.results[0].country"
15442 },
15443 {
15444 "into": "zone",
15445 "json": "json.results[0].timezone"
15446 },
15447 {
15448 "into": "height",
15449 "json": "json.results[0].elevation"
15450 }
15451 ],
15452 "http": {
15453 "method": "GET",
15454 "url": "https://geocoding-api.open-meteo.com/v1/search?name={{ inputs.place | url }}&count=1&language=en&format=json"
15455 },
15456 "into": "geo",
15457 "kind": "observe",
15458 "next": "found"
15459 },
15460 "has-height": {
15461 "branches": [
15462 {
15463 "then": "reply",
15464 "when": {
15465 "type": "number"
15466 }
15467 },
15468 {
15469 "else": "no-height"
15470 }
15471 ],
15472 "kind": "decide",
15473 "on": "height"
15474 },
15475 "no-height": {
15476 "kind": "fail",
15477 "reason": "no-height",
15478 "say": "Open-Meteo knows {{ state.name }} but not how high it is."
15479 },
15480 "reply": {
15481 "kind": "done",
15482 "output": {
15483 "country": "{{ state.country }}",
15484 "elevation_m": "{{ state.height }}",
15485 "place": "{{ state.name }}"
15486 },
15487 "say": "{{ state.name }}, {{ state.country }} is {{ state.height | int }} m above sea level."
15488 },
15489 "unknown-place": {
15490 "kind": "fail",
15491 "reason": "unknown-place",
15492 "say": "I could not find a place called {{ inputs.place }}."
15493 }
15494 },
15495 "start": "geocode",
15496 "tags": [
15497 "geography",
15498 "read-only"
15499 ],
15500 "triggers": [
15501 {
15502 "pattern": "how high (is|above sea level is) {place}"
15503 },
15504 {
15505 "pattern": "[the] (elevation|altitude) of {place}"
15506 }
15507 ],
15508 "version": 1
15509 },
15510 "exchange-rate": {
15511 "description": "An amount in another currency at today's European Central Bank reference rate, from Frankfurter.",
15512 "hosts": [
15513 "api.frankfurter.dev"
15514 ],
15515 "inputs": {
15516 "amount": {
15517 "default": "1",
15518 "description": "How much of the first currency, as in: 100 or 2.5",
15519 "pattern": "^[0-9]+(\\.[0-9]+)?$",
15520 "type": "str"
15521 },
15522 "from": {
15523 "description": "A three-letter currency code, as in: USD",
15524 "pattern": "^[A-Za-z]{3}$",
15525 "type": "str"
15526 },
15527 "to": {
15528 "description": "A three-letter currency code, as in: EUR",
15529 "pattern": "^[A-Za-z]{3}$",
15530 "type": "str"
15531 }
15532 },
15533 "license": "MIT",
15534 "name": "exchange-rate",
15535 "namespace": "pyrtan",
15536 "nodes": {
15537 "fetch": {
15538 "extract": [
15539 {
15540 "into": "value",
15541 "regex": "\"rates\":\\s*\\{\\s*\"[A-Z]{3}\":\\s*(-?[0-9.]+(?:[eE][-+]?[0-9]+)?)\\s*\\}"
15542 },
15543 {
15544 "into": "day",
15545 "json": "json.date"
15546 }
15547 ],
15548 "http": {
15549 "method": "GET",
15550 "url": "https://api.frankfurter.dev/v1/latest?base={{ inputs.from | upper }}&symbols={{ inputs.to | upper }}&amount={{ inputs.amount }}"
15551 },
15552 "into": "resp",
15553 "kind": "observe",
15554 "next": "status"
15555 },
15556 "has-rate": {
15557 "branches": [
15558 {
15559 "then": "reply",
15560 "when": {
15561 "exists": true
15562 }
15563 },
15564 {
15565 "else": "unexpected"
15566 }
15567 ],
15568 "kind": "decide",
15569 "on": "value"
15570 },
15571 "reply": {
15572 "kind": "done",
15573 "output": {
15574 "amount": "{{ inputs.amount | float }}",
15575 "date": "{{ state.day }}",
15576 "from": "{{ inputs.from | upper }}",
15577 "to": "{{ inputs.to | upper }}",
15578 "value": "{{ state.value | float }}"
15579 },
15580 "say": "{{ inputs.amount }} {{ inputs.from | upper }} is {{ state.value }} {{ inputs.to | upper }} at the ECB rate of {{ state.day }}."
15581 },
15582 "same-currency": {
15583 "kind": "fail",
15584 "reason": "same-currency",
15585 "say": "{{ inputs.from | upper }} to {{ inputs.to | upper }} is the same currency."
15586 },
15587 "status": {
15588 "branches": [
15589 {
15590 "then": "has-rate",
15591 "when": {
15592 "eq": 200
15593 }
15594 },
15595 {
15596 "then": "unknown-currency",
15597 "when": {
15598 "eq": 404
15599 }
15600 },
15601 {
15602 "then": "same-currency",
15603 "when": {
15604 "eq": 422
15605 }
15606 },
15607 {
15608 "else": "unexpected"
15609 }
15610 ],
15611 "kind": "decide",
15612 "on": "resp.status"
15613 },
15614 "unexpected": {
15615 "kind": "fail",
15616 "reason": "unexpected-status",
15617 "say": "Frankfurter answered {{ state.resp.status }}."
15618 },
15619 "unknown-currency": {
15620 "kind": "fail",
15621 "reason": "unknown-currency",
15622 "say": "The ECB publishes no rate between {{ inputs.from | upper }} and {{ inputs.to | upper }}; it covers about thirty currencies, by their three-letter codes."
15623 }
15624 },
15625 "start": "fetch",
15626 "tags": [
15627 "money",
15628 "read-only"
15629 ],
15630 "triggers": [
15631 {
15632 "pattern": "convert {amount:number} {from:word} (to|into|in) {to:word}"
15633 },
15634 {
15635 "pattern": "how much is {amount:number} {from:word} in {to:word}"
15636 },
15637 {
15638 "pattern": "[the] exchange rate (from|for) {from:word} to {to:word}"
15639 },
15640 {
15641 "pattern": "{from:word} to {to:word} (rate|exchange rate)"
15642 }
15643 ],
15644 "version": 1
15645 },
15646 "forecast": {
15647 "description": "Tomorrow's weather for a place: sky, high and low, and chance of rain, from Open-Meteo.",
15648 "hosts": [
15649 "geocoding-api.open-meteo.com",
15650 "api.open-meteo.com"
15651 ],
15652 "inputs": {
15653 "place": {
15654 "description": "A town or city name",
15655 "type": "str"
15656 }
15657 },
15658 "license": "MIT",
15659 "name": "forecast",
15660 "namespace": "pyrtan",
15661 "nodes": {
15662 "clear": {
15663 "kind": "set",
15664 "next": "reply",
15665 "set": {
15666 "sky": "clear"
15667 }
15668 },
15669 "cloudy": {
15670 "kind": "set",
15671 "next": "reply",
15672 "set": {
15673 "sky": "cloudy"
15674 }
15675 },
15676 "fog": {
15677 "kind": "set",
15678 "next": "reply",
15679 "set": {
15680 "sky": "foggy"
15681 }
15682 },
15683 "found": {
15684 "branches": [
15685 {
15686 "then": "tomorrow",
15687 "when": {
15688 "exists": true
15689 }
15690 },
15691 {
15692 "else": "unknown-place"
15693 }
15694 ],
15695 "kind": "decide",
15696 "on": "hit"
15697 },
15698 "geocode": {
15699 "extract": [
15700 {
15701 "into": "hit",
15702 "json": "json.results[0]"
15703 },
15704 {
15705 "into": "lat",
15706 "json": "json.results[0].latitude"
15707 },
15708 {
15709 "into": "lon",
15710 "json": "json.results[0].longitude"
15711 },
15712 {
15713 "into": "name",
15714 "json": "json.results[0].name"
15715 },
15716 {
15717 "into": "country",
15718 "json": "json.results[0].country"
15719 },
15720 {
15721 "into": "zone",
15722 "json": "json.results[0].timezone"
15723 },
15724 {
15725 "into": "height",
15726 "json": "json.results[0].elevation"
15727 }
15728 ],
15729 "http": {
15730 "method": "GET",
15731 "url": "https://geocoding-api.open-meteo.com/v1/search?name={{ inputs.place | url }}&count=1&language=en&format=json"
15732 },
15733 "into": "geo",
15734 "kind": "observe",
15735 "next": "found"
15736 },
15737 "have-day": {
15738 "branches": [
15739 {
15740 "then": "sky",
15741 "when": {
15742 "type": "number"
15743 }
15744 },
15745 {
15746 "else": "no-forecast"
15747 }
15748 ],
15749 "kind": "decide",
15750 "on": "high"
15751 },
15752 "no-forecast": {
15753 "kind": "fail",
15754 "reason": "no-forecast",
15755 "say": "Open-Meteo found {{ state.name }} but gave no forecast for tomorrow (status {{ state.fc.status }})."
15756 },
15757 "rain": {
15758 "kind": "set",
15759 "next": "reply",
15760 "set": {
15761 "sky": "rain"
15762 }
15763 },
15764 "reply": {
15765 "kind": "done",
15766 "output": {
15767 "country": "{{ state.country }}",
15768 "date": "{{ state.day }}",
15769 "high": "{{ state.high }}",
15770 "low": "{{ state.low }}",
15771 "place": "{{ state.name }}",
15772 "rain_chance": "{{ state.rain }}",
15773 "sky": "{{ state.sky }}",
15774 "unit": "{{ state.unit }}"
15775 },
15776 "say": "{{ state.name }}, {{ state.country }}, tomorrow ({{ state.day }}): {{ state.sky }}, {{ state.low }} to {{ state.high }}{{ state.unit }}, {{ state.rain }}% chance of rain."
15777 },
15778 "sky": {
15779 "branches": [
15780 {
15781 "then": "clear",
15782 "when": {
15783 "eq": 0
15784 }
15785 },
15786 {
15787 "then": "cloudy",
15788 "when": {
15789 "between": [
15790 1,
15791 3
15792 ]
15793 }
15794 },
15795 {
15796 "then": "fog",
15797 "when": {
15798 "between": [
15799 45,
15800 48
15801 ]
15802 }
15803 },
15804 {
15805 "then": "rain",
15806 "when": {
15807 "between": [
15808 51,
15809 67
15810 ]
15811 }
15812 },
15813 {
15814 "then": "snow",
15815 "when": {
15816 "between": [
15817 71,
15818 77
15819 ]
15820 }
15821 },
15822 {
15823 "then": "rain",
15824 "when": {
15825 "between": [
15826 80,
15827 82
15828 ]
15829 }
15830 },
15831 {
15832 "then": "snow",
15833 "when": {
15834 "between": [
15835 85,
15836 86
15837 ]
15838 }
15839 },
15840 {
15841 "then": "storm",
15842 "when": {
15843 "between": [
15844 95,
15845 99
15846 ]
15847 }
15848 },
15849 {
15850 "else": "unknown-sky"
15851 }
15852 ],
15853 "kind": "decide",
15854 "on": "code"
15855 },
15856 "snow": {
15857 "kind": "set",
15858 "next": "reply",
15859 "set": {
15860 "sky": "snow"
15861 }
15862 },
15863 "storm": {
15864 "kind": "set",
15865 "next": "reply",
15866 "set": {
15867 "sky": "storms"
15868 }
15869 },
15870 "tomorrow": {
15871 "extract": [
15872 {
15873 "into": "day",
15874 "json": "json.daily.time[1]"
15875 },
15876 {
15877 "into": "code",
15878 "json": "json.daily.weather_code[1]"
15879 },
15880 {
15881 "into": "high",
15882 "json": "json.daily.temperature_2m_max[1]"
15883 },
15884 {
15885 "into": "low",
15886 "json": "json.daily.temperature_2m_min[1]"
15887 },
15888 {
15889 "into": "rain",
15890 "json": "json.daily.precipitation_probability_max[1]"
15891 },
15892 {
15893 "into": "unit",
15894 "json": "json.daily_units.temperature_2m_max"
15895 }
15896 ],
15897 "http": {
15898 "method": "GET",
15899 "url": "https://api.open-meteo.com/v1/forecast?latitude={{ state.lat }}&longitude={{ state.lon }}&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max&timezone=auto&forecast_days=2"
15900 },
15901 "into": "fc",
15902 "kind": "observe",
15903 "next": "have-day"
15904 },
15905 "unknown-place": {
15906 "kind": "fail",
15907 "reason": "unknown-place",
15908 "say": "I could not find a place called {{ inputs.place }}."
15909 },
15910 "unknown-sky": {
15911 "kind": "set",
15912 "next": "reply",
15913 "set": {
15914 "sky": "weather code {{ state.code }}"
15915 }
15916 }
15917 },
15918 "start": "geocode",
15919 "tags": [
15920 "weather",
15921 "read-only"
15922 ],
15923 "triggers": [
15924 {
15925 "pattern": "[what's the] weather (in|for) {place} tomorrow"
15926 },
15927 {
15928 "pattern": "[what's the] weather tomorrow (in|for) {place}"
15929 },
15930 {
15931 "pattern": "will it rain tomorrow in {place}"
15932 }
15933 ],
15934 "version": 1
15935 },
15936 "github-release": {
15937 "description": "The latest release of a GitHub repository.",
15938 "hosts": [
15939 "api.github.com"
15940 ],
15941 "inputs": {
15942 "repo": {
15943 "description": "owner/name",
15944 "pattern": "^[A-Za-z0-9_.\\-]+/[A-Za-z0-9_.\\-]+$",
15945 "type": "str"
15946 }
15947 },
15948 "license": "MIT",
15949 "name": "github-release",
15950 "namespace": "pyrtan",
15951 "nodes": {
15952 "fetch": {
15953 "extract": [
15954 {
15955 "into": "tag",
15956 "json": "json.tag_name"
15957 },
15958 {
15959 "into": "name",
15960 "json": "json.name"
15961 },
15962 {
15963 "into": "published",
15964 "json": "json.published_at"
15965 },
15966 {
15967 "into": "url",
15968 "json": "json.html_url"
15969 },
15970 {
15971 "into": "prerelease",
15972 "json": "json.prerelease"
15973 }
15974 ],
15975 "http": {
15976 "headers": {
15977 "Accept": "application/vnd.github+json"
15978 },
15979 "method": "GET",
15980 "url": "https://api.github.com/repos/{{ inputs.repo }}/releases/latest"
15981 },
15982 "into": "resp",
15983 "kind": "observe",
15984 "next": "status"
15985 },
15986 "none": {
15987 "kind": "fail",
15988 "reason": "no-release",
15989 "say": "{{ inputs.repo }} has no releases, or does not exist."
15990 },
15991 "rate-limited": {
15992 "kind": "fail",
15993 "reason": "rate-limited",
15994 "say": "GitHub is rate-limiting unauthenticated requests ({{ state.resp.status }})."
15995 },
15996 "reply": {
15997 "kind": "done",
15998 "output": {
15999 "name": "{{ state.name }}",
16000 "prerelease": "{{ state.prerelease }}",
16001 "published": "{{ state.published }}",
16002 "repo": "{{ inputs.repo }}",
16003 "tag": "{{ state.tag }}",
16004 "url": "{{ state.url }}"
16005 },
16006 "say": "{{ inputs.repo }}: {{ state.tag }} ({{ state.published }}) {{ state.url }}"
16007 },
16008 "status": {
16009 "branches": [
16010 {
16011 "then": "reply",
16012 "when": {
16013 "eq": 200
16014 }
16015 },
16016 {
16017 "then": "none",
16018 "when": {
16019 "eq": 404
16020 }
16021 },
16022 {
16023 "then": "rate-limited",
16024 "when": {
16025 "in": [
16026 403,
16027 429
16028 ]
16029 }
16030 },
16031 {
16032 "else": "unexpected"
16033 }
16034 ],
16035 "kind": "decide",
16036 "on": "resp.status"
16037 },
16038 "unexpected": {
16039 "kind": "fail",
16040 "reason": "unexpected-response",
16041 "say": "GitHub answered {{ state.resp.status }}."
16042 }
16043 },
16044 "start": "fetch",
16045 "tags": [
16046 "github",
16047 "read-only"
16048 ],
16049 "triggers": [
16050 {
16051 "pattern": "[what's the] latest release of {repo:word}"
16052 },
16053 {
16054 "pattern": "latest {repo:word} release"
16055 }
16056 ],
16057 "version": 2
16058 },
16059 "github-repo": {
16060 "description": "A GitHub repository at a glance: description, language, stars, forks, open issues.",
16061 "hosts": [
16062 "api.github.com"
16063 ],
16064 "inputs": {
16065 "repo": {
16066 "description": "owner/name, as in: astral-sh/uv",
16067 "pattern": "^[A-Za-z0-9_.\\-]+/[A-Za-z0-9_.\\-]+$",
16068 "type": "str"
16069 }
16070 },
16071 "license": "MIT",
16072 "name": "github-repo",
16073 "namespace": "pyrtan",
16074 "nodes": {
16075 "fetch": {
16076 "extract": [
16077 {
16078 "into": "full",
16079 "json": "json.full_name"
16080 },
16081 {
16082 "into": "about",
16083 "json": "json.description"
16084 },
16085 {
16086 "into": "language",
16087 "json": "json.language"
16088 },
16089 {
16090 "into": "stars",
16091 "json": "json.stargazers_count"
16092 },
16093 {
16094 "into": "forks",
16095 "json": "json.forks_count"
16096 },
16097 {
16098 "into": "issues",
16099 "json": "json.open_issues_count"
16100 },
16101 {
16102 "into": "url",
16103 "json": "json.html_url"
16104 },
16105 {
16106 "into": "archived",
16107 "json": "json.archived"
16108 }
16109 ],
16110 "http": {
16111 "headers": {
16112 "Accept": "application/vnd.github+json"
16113 },
16114 "method": "GET",
16115 "url": "https://api.github.com/repos/{{ inputs.repo }}"
16116 },
16117 "into": "resp",
16118 "kind": "observe",
16119 "next": "status"
16120 },
16121 "no-repo": {
16122 "kind": "fail",
16123 "reason": "no-repo",
16124 "say": "GitHub has no public repository {{ inputs.repo }}."
16125 },
16126 "rate-limited": {
16127 "kind": "fail",
16128 "reason": "rate-limited",
16129 "say": "GitHub is rate-limiting unauthenticated requests ({{ state.resp.status }})."
16130 },
16131 "reply": {
16132 "kind": "done",
16133 "output": {
16134 "archived": "{{ state.archived }}",
16135 "description": "{{ state.about }}",
16136 "forks": "{{ state.forks }}",
16137 "language": "{{ state.language }}",
16138 "open_issues": "{{ state.issues }}",
16139 "repo": "{{ state.full }}",
16140 "stars": "{{ state.stars }}",
16141 "url": "{{ state.url }}"
16142 },
16143 "say": "{{ state.full }}: {{ state.about }} ({{ state.language }}) · {{ state.stars }} stars, {{ state.forks }} forks, {{ state.issues }} open issues. {{ state.url }}"
16144 },
16145 "status": {
16146 "branches": [
16147 {
16148 "then": "reply",
16149 "when": {
16150 "eq": 200
16151 }
16152 },
16153 {
16154 "then": "no-repo",
16155 "when": {
16156 "eq": 404
16157 }
16158 },
16159 {
16160 "then": "rate-limited",
16161 "when": {
16162 "in": [
16163 403,
16164 429
16165 ]
16166 }
16167 },
16168 {
16169 "else": "unexpected"
16170 }
16171 ],
16172 "kind": "decide",
16173 "on": "resp.status"
16174 },
16175 "unexpected": {
16176 "kind": "fail",
16177 "reason": "unexpected-status",
16178 "say": "GitHub answered {{ state.resp.status }}."
16179 }
16180 },
16181 "start": "fetch",
16182 "tags": [
16183 "github",
16184 "software",
16185 "read-only"
16186 ],
16187 "triggers": [
16188 {
16189 "pattern": "how many stars does {repo:word} have"
16190 },
16191 {
16192 "pattern": "github (repo|repository) {repo:word}"
16193 },
16194 {
16195 "pattern": "tell me about the {repo:word} repo"
16196 }
16197 ],
16198 "version": 1
16199 },
16200 "go-module": {
16201 "description": "The latest version of a Go module, and when it was published, from proxy.golang.org.",
16202 "hosts": [
16203 "proxy.golang.org"
16204 ],
16205 "inputs": {
16206 "module": {
16207 "description": "A module path, as in: github.com/gorilla/mux",
16208 "pattern": "^[a-z0-9.\\-]+(/[A-Za-z0-9._~\\-]+)+$",
16209 "type": "str"
16210 }
16211 },
16212 "license": "MIT",
16213 "name": "go-module",
16214 "namespace": "pyrtan",
16215 "nodes": {
16216 "fetch": {
16217 "extract": [
16218 {
16219 "into": "version",
16220 "json": "json.Version"
16221 },
16222 {
16223 "from": "json.Time",
16224 "into": "day",
16225 "regex": "^(\\d{4}-\\d\\d-\\d\\d)"
16226 }
16227 ],
16228 "http": {
16229 "method": "GET",
16230 "url": "https://proxy.golang.org/{{ inputs.module | lower }}/@latest"
16231 },
16232 "into": "resp",
16233 "kind": "observe",
16234 "next": "status"
16235 },
16236 "no-module": {
16237 "kind": "fail",
16238 "reason": "no-module",
16239 "say": "The Go module proxy has no module {{ inputs.module }}."
16240 },
16241 "reply": {
16242 "kind": "done",
16243 "output": {
16244 "module": "{{ inputs.module }}",
16245 "published": "{{ state.day }}",
16246 "url": "https://pkg.go.dev/{{ inputs.module }}",
16247 "version": "{{ state.version }}"
16248 },
16249 "say": "{{ inputs.module }} {{ state.version }}, published {{ state.day }}. https://pkg.go.dev/{{ inputs.module }}"
16250 },
16251 "status": {
16252 "branches": [
16253 {
16254 "then": "reply",
16255 "when": {
16256 "eq": 200
16257 }
16258 },
16259 {
16260 "then": "no-module",
16261 "when": {
16262 "in": [
16263 404,
16264 410
16265 ]
16266 }
16267 },
16268 {
16269 "else": "unexpected"
16270 }
16271 ],
16272 "kind": "decide",
16273 "on": "resp.status"
16274 },
16275 "unexpected": {
16276 "kind": "fail",
16277 "reason": "unexpected-status",
16278 "say": "The Go module proxy answered {{ state.resp.status }}."
16279 }
16280 },
16281 "start": "fetch",
16282 "tags": [
16283 "software",
16284 "go",
16285 "read-only"
16286 ],
16287 "triggers": [
16288 {
16289 "pattern": "[what's the] latest version of {module:word} on go"
16290 },
16291 {
16292 "pattern": "go (module|version) {module:word}"
16293 }
16294 ],
16295 "version": 1
16296 },
16297 "hacker-news": {
16298 "description": "Top stories on Hacker News, from its public API.",
16299 "hosts": [
16300 "hacker-news.firebaseio.com"
16301 ],
16302 "inputs": {
16303 "count": {
16304 "default": 1,
16305 "description": "How many stories to return",
16306 "max": 30,
16307 "min": 1,
16308 "type": "int"
16309 }
16310 },
16311 "license": "MIT",
16312 "name": "hacker-news",
16313 "namespace": "pyrtan",
16314 "nodes": {
16315 "any": {
16316 "branches": [
16317 {
16318 "then": "nothing",
16319 "when": {
16320 "empty": true
16321 }
16322 },
16323 {
16324 "then": "stories",
16325 "when": {
16326 "type": "list"
16327 }
16328 },
16329 {
16330 "else": "unexpected"
16331 }
16332 ],
16333 "kind": "decide",
16334 "on": "ids"
16335 },
16336 "check": {
16337 "branches": [
16338 {
16339 "then": "any",
16340 "when": {
16341 "eq": 200
16342 }
16343 },
16344 {
16345 "then": "hn-down",
16346 "when": {
16347 "gte": 500
16348 }
16349 },
16350 {
16351 "else": "unexpected"
16352 }
16353 ],
16354 "kind": "decide",
16355 "on": "ids_resp.status"
16356 },
16357 "fetch-ids": {
16358 "extract": [
16359 {
16360 "into": "ids",
16361 "json": "json"
16362 }
16363 ],
16364 "http": {
16365 "method": "GET",
16366 "url": "https://hacker-news.firebaseio.com/v0/topstories.json"
16367 },
16368 "into": "ids_resp",
16369 "kind": "observe",
16370 "next": "check"
16371 },
16372 "hn-down": {
16373 "kind": "fail",
16374 "reason": "upstream-down",
16375 "say": "Hacker News answered {{ state.ids_resp.status }}; try again later."
16376 },
16377 "nothing": {
16378 "kind": "fail",
16379 "reason": "no-stories",
16380 "say": "Hacker News returned an empty front page."
16381 },
16382 "reply": {
16383 "kind": "done",
16384 "output": {
16385 "stories": "{{ state.stories }}"
16386 },
16387 "say": "Top of Hacker News: {{ state.stories[0].title }} — {{ state.stories[0].url }}"
16388 },
16389 "stories": {
16390 "into": "stories",
16391 "kind": "each",
16392 "limit": "{{ inputs.count }}",
16393 "next": "reply",
16394 "over": "ids",
16395 "tree": "hacker-news-item",
16396 "with": {
16397 "id": "{{ item }}"
16398 }
16399 },
16400 "unexpected": {
16401 "kind": "fail",
16402 "reason": "unexpected-response",
16403 "say": "Hacker News answered {{ state.ids_resp.status }} with something this tree does not recognise."
16404 }
16405 },
16406 "start": "fetch-ids",
16407 "tags": [
16408 "news",
16409 "read-only"
16410 ],
16411 "triggers": [
16412 {
16413 "inputs": {
16414 "count": 1
16415 },
16416 "pattern": "[what's the] top story on hacker news"
16417 },
16418 {
16419 "pattern": "top {count:int} stories on hacker news"
16420 }
16421 ],
16422 "version": 2
16423 },
16424 "hacker-news-item": {
16425 "description": "One Hacker News item by id: title, url, score, author.",
16426 "hosts": [
16427 "hacker-news.firebaseio.com"
16428 ],
16429 "inputs": {
16430 "id": {
16431 "description": "The item id",
16432 "min": 1,
16433 "type": "int"
16434 }
16435 },
16436 "license": "MIT",
16437 "name": "hacker-news-item",
16438 "namespace": "pyrtan",
16439 "nodes": {
16440 "fetch": {
16441 "extract": [
16442 {
16443 "into": "title",
16444 "json": "json.title"
16445 },
16446 {
16447 "into": "url",
16448 "json": "json.url"
16449 },
16450 {
16451 "into": "score",
16452 "json": "json.score"
16453 },
16454 {
16455 "into": "by",
16456 "json": "json.by"
16457 },
16458 {
16459 "into": "type",
16460 "json": "json.type"
16461 }
16462 ],
16463 "http": {
16464 "method": "GET",
16465 "url": "https://hacker-news.firebaseio.com/v0/item/{{ inputs.id }}.json"
16466 },
16467 "into": "resp",
16468 "kind": "observe",
16469 "next": "kind"
16470 },
16471 "has-url": {
16472 "branches": [
16473 {
16474 "then": "reply",
16475 "when": {
16476 "exists": true
16477 }
16478 },
16479 {
16480 "else": "self-post"
16481 }
16482 ],
16483 "kind": "decide",
16484 "on": "url"
16485 },
16486 "kind": {
16487 "branches": [
16488 {
16489 "then": "has-url",
16490 "when": {
16491 "eq": "story"
16492 }
16493 },
16494 {
16495 "then": "has-url",
16496 "when": {
16497 "eq": "job"
16498 }
16499 },
16500 {
16501 "then": "missing",
16502 "when": {
16503 "exists": false
16504 }
16505 },
16506 {
16507 "else": "not-a-story"
16508 }
16509 ],
16510 "kind": "decide",
16511 "on": "type"
16512 },
16513 "missing": {
16514 "kind": "fail",
16515 "reason": "no-such-item",
16516 "say": "There is no Hacker News item {{ inputs.id }}."
16517 },
16518 "not-a-story": {
16519 "kind": "fail",
16520 "reason": "not-a-story",
16521 "say": "Item {{ inputs.id }} is a {{ state.type }}, not a story."
16522 },
16523 "reply": {
16524 "kind": "done",
16525 "output": {
16526 "by": "{{ state.by }}",
16527 "id": "{{ inputs.id }}",
16528 "score": "{{ state.score }}",
16529 "title": "{{ state.title }}",
16530 "url": "{{ state.url }}"
16531 },
16532 "say": "{{ state.title }} ({{ state.score }} points, by {{ state.by }}) {{ state.url }}"
16533 },
16534 "self-post": {
16535 "kind": "set",
16536 "next": "reply",
16537 "set": {
16538 "url": "https://news.ycombinator.com/item?id={{ inputs.id }}"
16539 }
16540 }
16541 },
16542 "start": "fetch",
16543 "tags": [
16544 "news",
16545 "read-only"
16546 ],
16547 "triggers": [
16548 {
16549 "pattern": "hacker news item {id:int}"
16550 }
16551 ],
16552 "version": 2
16553 },
16554 "hn-search": {
16555 "description": "Hacker News stories about something, through HN Search: the top three matches.",
16556 "hosts": [
16557 "hn.algolia.com"
16558 ],
16559 "inputs": {
16560 "query": {
16561 "description": "Words to search story titles and text for",
16562 "type": "str"
16563 }
16564 },
16565 "license": "MIT",
16566 "name": "hn-search",
16567 "namespace": "pyrtan",
16568 "nodes": {
16569 "any": {
16570 "branches": [
16571 {
16572 "then": "nothing",
16573 "when": {
16574 "empty": true
16575 }
16576 },
16577 {
16578 "else": "each"
16579 }
16580 ],
16581 "kind": "decide",
16582 "on": "hits"
16583 },
16584 "each": {
16585 "into": "stories",
16586 "kind": "each",
16587 "limit": 3,
16588 "next": "reply",
16589 "over": "hits",
16590 "start": "one"
16591 },
16592 "nothing": {
16593 "kind": "done",
16594 "output": {
16595 "query": "{{ inputs.query }}",
16596 "stories": [],
16597 "total": 0
16598 },
16599 "say": "No Hacker News stories match {{ inputs.query }}."
16600 },
16601 "one": {
16602 "kind": "done",
16603 "output": {
16604 "by": "{{ item.author }}",
16605 "discussion": "https://news.ycombinator.com/item?id={{ item.objectID }}",
16606 "points": "{{ item.points }}",
16607 "title": "{{ item.title }}",
16608 "url": "{{ item.url }}"
16609 }
16610 },
16611 "reply": {
16612 "kind": "done",
16613 "output": {
16614 "query": "{{ inputs.query }}",
16615 "stories": "{{ state.stories }}",
16616 "total": "{{ state.total }}"
16617 },
16618 "say": "{{ state.total }} Hacker News stories match {{ inputs.query }}. Top: {{ state.first }} {{ state.first_url }}"
16619 },
16620 "search": {
16621 "extract": [
16622 {
16623 "into": "total",
16624 "json": "json.nbHits"
16625 },
16626 {
16627 "into": "first",
16628 "json": "json.hits[0].title"
16629 },
16630 {
16631 "into": "first_url",
16632 "json": "json.hits[0].url"
16633 },
16634 {
16635 "into": "hits",
16636 "json": "json.hits"
16637 }
16638 ],
16639 "http": {
16640 "method": "GET",
16641 "url": "https://hn.algolia.com/api/v1/search?query={{ inputs.query | url }}&tags=story&hitsPerPage=3"
16642 },
16643 "into": "resp",
16644 "kind": "observe",
16645 "next": "status"
16646 },
16647 "status": {
16648 "branches": [
16649 {
16650 "then": "any",
16651 "when": {
16652 "eq": 200
16653 }
16654 },
16655 {
16656 "else": "unexpected"
16657 }
16658 ],
16659 "kind": "decide",
16660 "on": "resp.status"
16661 },
16662 "unexpected": {
16663 "kind": "fail",
16664 "reason": "unexpected-status",
16665 "say": "HN Search answered {{ state.resp.status }}."
16666 }
16667 },
16668 "start": "search",
16669 "tags": [
16670 "news",
16671 "tech",
16672 "read-only"
16673 ],
16674 "triggers": [
16675 {
16676 "pattern": "search hacker news for {query}"
16677 },
16678 {
16679 "pattern": "hacker news stories about {query}"
16680 },
16681 {
16682 "pattern": "what does hacker news say about {query}"
16683 }
16684 ],
16685 "version": 1
16686 },
16687 "holidays": {
16688 "description": "The next public holiday in a country (by its two-letter code), from Nager.Date.",
16689 "hosts": [
16690 "date.nager.at"
16691 ],
16692 "inputs": {
16693 "country": {
16694 "description": "A two-letter country code, as in: NO, US, DE",
16695 "pattern": "^[A-Za-z]{2}$",
16696 "type": "str"
16697 }
16698 },
16699 "license": "MIT",
16700 "name": "holidays",
16701 "namespace": "pyrtan",
16702 "nodes": {
16703 "any": {
16704 "branches": [
16705 {
16706 "then": "reply",
16707 "when": {
16708 "exists": true
16709 }
16710 },
16711 {
16712 "else": "none-listed"
16713 }
16714 ],
16715 "kind": "decide",
16716 "on": "date"
16717 },
16718 "fetch": {
16719 "extract": [
16720 {
16721 "into": "date",
16722 "json": "json[0].date"
16723 },
16724 {
16725 "into": "name",
16726 "json": "json[0].name"
16727 },
16728 {
16729 "into": "local",
16730 "json": "json[0].localName"
16731 },
16732 {
16733 "into": "then",
16734 "json": "json[1].name"
16735 },
16736 {
16737 "into": "then_date",
16738 "json": "json[1].date"
16739 }
16740 ],
16741 "http": {
16742 "method": "GET",
16743 "url": "https://date.nager.at/api/v3/NextPublicHolidays/{{ inputs.country | upper }}"
16744 },
16745 "into": "resp",
16746 "kind": "observe",
16747 "next": "status"
16748 },
16749 "none-listed": {
16750 "kind": "fail",
16751 "reason": "none-listed",
16752 "say": "Nager.Date lists no coming public holidays for {{ inputs.country | upper }}."
16753 },
16754 "reply": {
16755 "kind": "done",
16756 "output": {
16757 "after": {
16758 "date": "{{ state.then_date }}",
16759 "name": "{{ state.then }}"
16760 },
16761 "country": "{{ inputs.country | upper }}",
16762 "date": "{{ state.date }}",
16763 "local_name": "{{ state.local }}",
16764 "name": "{{ state.name }}"
16765 },
16766 "say": "The next public holiday in {{ inputs.country | upper }} is {{ state.name }} ({{ state.local }}) on {{ state.date }}."
16767 },
16768 "status": {
16769 "branches": [
16770 {
16771 "then": "any",
16772 "when": {
16773 "eq": 200
16774 }
16775 },
16776 {
16777 "else": "unknown-country"
16778 }
16779 ],
16780 "kind": "decide",
16781 "on": "resp.status"
16782 },
16783 "unknown-country": {
16784 "kind": "fail",
16785 "reason": "unknown-country",
16786 "say": "Nager.Date knows no country {{ inputs.country | upper }} (it answered {{ state.resp.status }}); use a two-letter code such as NO, US or DE."
16787 }
16788 },
16789 "start": "fetch",
16790 "tags": [
16791 "calendar",
16792 "read-only"
16793 ],
16794 "triggers": [
16795 {
16796 "pattern": "[when is the] next [public|bank] holiday in {country:word}"
16797 },
16798 {
16799 "pattern": "[public|bank] holidays in {country:word}"
16800 }
16801 ],
16802 "version": 1
16803 },
16804 "homebrew": {
16805 "description": "A Homebrew formula's current stable version and what it is, from formulae.brew.sh.",
16806 "hosts": [
16807 "formulae.brew.sh"
16808 ],
16809 "inputs": {
16810 "formula": {
16811 "description": "A formula name, as in: wget",
16812 "pattern": "^[a-z0-9][a-z0-9@+._\\-]*$",
16813 "type": "str"
16814 }
16815 },
16816 "license": "MIT",
16817 "name": "homebrew",
16818 "namespace": "pyrtan",
16819 "nodes": {
16820 "fetch": {
16821 "extract": [
16822 {
16823 "into": "name",
16824 "json": "json.name"
16825 },
16826 {
16827 "into": "version",
16828 "json": "json.versions.stable"
16829 },
16830 {
16831 "into": "about",
16832 "json": "json.desc"
16833 },
16834 {
16835 "into": "home",
16836 "json": "json.homepage"
16837 }
16838 ],
16839 "http": {
16840 "method": "GET",
16841 "url": "https://formulae.brew.sh/api/formula/{{ inputs.formula | lower | url }}.json"
16842 },
16843 "into": "resp",
16844 "kind": "observe",
16845 "next": "status"
16846 },
16847 "no-formula": {
16848 "kind": "fail",
16849 "reason": "no-formula",
16850 "say": "Homebrew has no formula called {{ inputs.formula }}."
16851 },
16852 "reply": {
16853 "kind": "done",
16854 "output": {
16855 "description": "{{ state.about }}",
16856 "formula": "{{ state.name }}",
16857 "homepage": "{{ state.home }}",
16858 "install": "brew install {{ state.name }}",
16859 "version": "{{ state.version }}"
16860 },
16861 "say": "{{ state.name }} {{ state.version }} on Homebrew: {{ state.about }}. {{ state.home }}"
16862 },
16863 "status": {
16864 "branches": [
16865 {
16866 "then": "reply",
16867 "when": {
16868 "eq": 200
16869 }
16870 },
16871 {
16872 "then": "no-formula",
16873 "when": {
16874 "eq": 404
16875 }
16876 },
16877 {
16878 "else": "unexpected"
16879 }
16880 ],
16881 "kind": "decide",
16882 "on": "resp.status"
16883 },
16884 "unexpected": {
16885 "kind": "fail",
16886 "reason": "unexpected-status",
16887 "say": "formulae.brew.sh answered {{ state.resp.status }}."
16888 }
16889 },
16890 "start": "fetch",
16891 "tags": [
16892 "software",
16893 "macos",
16894 "read-only"
16895 ],
16896 "triggers": [
16897 {
16898 "pattern": "[what's the] latest version of {formula:word} on homebrew"
16899 },
16900 {
16901 "pattern": "(brew|homebrew) (formula|version) {formula:word}"
16902 }
16903 ],
16904 "version": 1
16905 },
16906 "iss": {
16907 "description": "Where the International Space Station is right now: latitude, longitude, altitude and speed.",
16908 "hosts": [
16909 "api.wheretheiss.at"
16910 ],
16911 "license": "MIT",
16912 "name": "iss",
16913 "namespace": "pyrtan",
16914 "nodes": {
16915 "fetch": {
16916 "extract": [
16917 {
16918 "into": "lat",
16919 "json": "json.latitude"
16920 },
16921 {
16922 "into": "lon",
16923 "json": "json.longitude"
16924 },
16925 {
16926 "into": "alt",
16927 "json": "json.altitude"
16928 },
16929 {
16930 "into": "speed",
16931 "json": "json.velocity"
16932 },
16933 {
16934 "into": "light",
16935 "json": "json.visibility"
16936 },
16937 {
16938 "into": "lat2",
16939 "regex": "\"latitude\":\\s*(-?\\d+(?:\\.\\d{1,2})?)"
16940 },
16941 {
16942 "into": "lon2",
16943 "regex": "\"longitude\":\\s*(-?\\d+(?:\\.\\d{1,2})?)"
16944 }
16945 ],
16946 "http": {
16947 "method": "GET",
16948 "url": "https://api.wheretheiss.at/v1/satellites/25544"
16949 },
16950 "into": "resp",
16951 "kind": "observe",
16952 "next": "status"
16953 },
16954 "have-position": {
16955 "branches": [
16956 {
16957 "then": "light",
16958 "when": {
16959 "type": "number"
16960 }
16961 },
16962 {
16963 "else": "unexpected"
16964 }
16965 ],
16966 "kind": "decide",
16967 "on": "lat"
16968 },
16969 "in-shadow": {
16970 "kind": "set",
16971 "next": "reply",
16972 "set": {
16973 "where": "in the Earth's shadow"
16974 }
16975 },
16976 "in-sun": {
16977 "kind": "set",
16978 "next": "reply",
16979 "set": {
16980 "where": "in sunlight"
16981 }
16982 },
16983 "light": {
16984 "branches": [
16985 {
16986 "then": "in-sun",
16987 "when": {
16988 "eq": "daylight"
16989 }
16990 },
16991 {
16992 "then": "in-shadow",
16993 "when": {
16994 "eq": "eclipsed"
16995 }
16996 },
16997 {
16998 "else": "in-sun"
16999 }
17000 ],
17001 "kind": "decide",
17002 "on": "light"
17003 },
17004 "rate-limited": {
17005 "kind": "fail",
17006 "reason": "rate-limited",
17007 "say": "wheretheiss.at is asking for fewer requests (429); try again in a moment."
17008 },
17009 "reply": {
17010 "kind": "done",
17011 "output": {
17012 "altitude_km": "{{ state.alt }}",
17013 "latitude": "{{ state.lat }}",
17014 "longitude": "{{ state.lon }}",
17015 "map": "https://www.openstreetmap.org/?mlat={{ state.lat }}&mlon={{ state.lon }}&zoom=3",
17016 "speed_kmh": "{{ state.speed }}",
17017 "visibility": "{{ state.light }}"
17018 },
17019 "say": "The ISS is over {{ state.lat2 }}, {{ state.lon2 }}, {{ state.alt | int }} km up, moving at {{ state.speed | int }} km/h, {{ state.where }}."
17020 },
17021 "status": {
17022 "branches": [
17023 {
17024 "then": "have-position",
17025 "when": {
17026 "eq": 200
17027 }
17028 },
17029 {
17030 "then": "rate-limited",
17031 "when": {
17032 "eq": 429
17033 }
17034 },
17035 {
17036 "else": "unexpected"
17037 }
17038 ],
17039 "kind": "decide",
17040 "on": "resp.status"
17041 },
17042 "unexpected": {
17043 "kind": "fail",
17044 "reason": "unexpected-status",
17045 "say": "wheretheiss.at answered {{ state.resp.status }}."
17046 }
17047 },
17048 "start": "fetch",
17049 "tags": [
17050 "science",
17051 "space",
17052 "read-only"
17053 ],
17054 "triggers": [
17055 {
17056 "pattern": "where is the (iss|space station) [now]"
17057 },
17058 {
17059 "pattern": "(iss|space station) (position|location)"
17060 }
17061 ],
17062 "version": 1
17063 },
17064 "local-time": {
17065 "description": "What time it is now in a place, and its time zone.",
17066 "hosts": [
17067 "geocoding-api.open-meteo.com",
17068 "timeapi.io"
17069 ],
17070 "inputs": {
17071 "place": {
17072 "description": "A town or city name",
17073 "type": "str"
17074 }
17075 },
17076 "license": "MIT",
17077 "name": "local-time",
17078 "namespace": "pyrtan",
17079 "nodes": {
17080 "clock": {
17081 "extract": [
17082 {
17083 "into": "time",
17084 "json": "json.time"
17085 },
17086 {
17087 "into": "weekday",
17088 "json": "json.dayOfWeek"
17089 },
17090 {
17091 "from": "json.dateTime",
17092 "into": "date",
17093 "regex": "^(\\d{4}-\\d\\d-\\d\\d)"
17094 }
17095 ],
17096 "http": {
17097 "method": "GET",
17098 "url": "https://timeapi.io/api/time/current/zone?timeZone={{ state.zone | url }}"
17099 },
17100 "into": "resp",
17101 "kind": "observe",
17102 "next": "status"
17103 },
17104 "found": {
17105 "branches": [
17106 {
17107 "then": "clock",
17108 "when": {
17109 "exists": true
17110 }
17111 },
17112 {
17113 "else": "unknown-place"
17114 }
17115 ],
17116 "kind": "decide",
17117 "on": "hit"
17118 },
17119 "geocode": {
17120 "extract": [
17121 {
17122 "into": "hit",
17123 "json": "json.results[0]"
17124 },
17125 {
17126 "into": "lat",
17127 "json": "json.results[0].latitude"
17128 },
17129 {
17130 "into": "lon",
17131 "json": "json.results[0].longitude"
17132 },
17133 {
17134 "into": "name",
17135 "json": "json.results[0].name"
17136 },
17137 {
17138 "into": "country",
17139 "json": "json.results[0].country"
17140 },
17141 {
17142 "into": "zone",
17143 "json": "json.results[0].timezone"
17144 },
17145 {
17146 "into": "height",
17147 "json": "json.results[0].elevation"
17148 }
17149 ],
17150 "http": {
17151 "method": "GET",
17152 "url": "https://geocoding-api.open-meteo.com/v1/search?name={{ inputs.place | url }}&count=1&language=en&format=json"
17153 },
17154 "into": "geo",
17155 "kind": "observe",
17156 "next": "found"
17157 },
17158 "no-clock": {
17159 "kind": "fail",
17160 "reason": "no-clock",
17161 "say": "timeapi.io gave no time for {{ state.zone }} (status {{ state.resp.status }})."
17162 },
17163 "reply": {
17164 "kind": "done",
17165 "output": {
17166 "country": "{{ state.country }}",
17167 "date": "{{ state.date }}",
17168 "place": "{{ state.name }}",
17169 "time": "{{ state.time }}",
17170 "timezone": "{{ state.zone }}",
17171 "weekday": "{{ state.weekday }}"
17172 },
17173 "say": "It is {{ state.time }} on {{ state.weekday }} {{ state.date }} in {{ state.name }}, {{ state.country }} ({{ state.zone }})."
17174 },
17175 "status": {
17176 "branches": [
17177 {
17178 "then": "reply",
17179 "when": {
17180 "eq": 200
17181 }
17182 },
17183 {
17184 "else": "no-clock"
17185 }
17186 ],
17187 "kind": "decide",
17188 "on": "resp.status"
17189 },
17190 "unknown-place": {
17191 "kind": "fail",
17192 "reason": "unknown-place",
17193 "say": "I could not find a place called {{ inputs.place }}."
17194 }
17195 },
17196 "start": "geocode",
17197 "tags": [
17198 "time",
17199 "read-only"
17200 ],
17201 "triggers": [
17202 {
17203 "pattern": "what time is it in {place}"
17204 },
17205 {
17206 "pattern": "[the] (time|local time) in {place}"
17207 }
17208 ],
17209 "version": 1
17210 },
17211 "log-errors": {
17212 "description": "Error lines in the tail of a log file on this box.",
17213 "inputs": {
17214 "lines": {
17215 "default": 200,
17216 "description": "How many lines from the end to look at",
17217 "max": 5000,
17218 "min": 1,
17219 "type": "int"
17220 },
17221 "path": {
17222 "default": "/var/log/syslog",
17223 "type": "str"
17224 }
17225 },
17226 "license": "MIT",
17227 "name": "log-errors",
17228 "namespace": "pyrtan",
17229 "nodes": {
17230 "any": {
17231 "branches": [
17232 {
17233 "then": "clean",
17234 "when": {
17235 "eq": 0
17236 }
17237 },
17238 {
17239 "else": "some"
17240 }
17241 ],
17242 "kind": "decide",
17243 "on": "n"
17244 },
17245 "clean": {
17246 "kind": "done",
17247 "output": {
17248 "errors": 0,
17249 "path": "{{ inputs.path }}"
17250 },
17251 "say": "No error lines in the last {{ state.seen }} of {{ inputs.path }}."
17252 },
17253 "count": {
17254 "kind": "set",
17255 "next": "any",
17256 "set": {
17257 "n": "{{ state.errors | length }}",
17258 "seen": "{{ state.log.lines | length }}"
17259 }
17260 },
17261 "exists": {
17262 "branches": [
17263 {
17264 "then": "count",
17265 "when": {
17266 "eq": true
17267 }
17268 },
17269 {
17270 "else": "missing"
17271 }
17272 ],
17273 "kind": "decide",
17274 "on": "log.exists"
17275 },
17276 "missing": {
17277 "kind": "fail",
17278 "reason": "no-such-file",
17279 "say": "{{ inputs.path }} is not on this box."
17280 },
17281 "read": {
17282 "extract": [
17283 {
17284 "all": true,
17285 "into": "errors",
17286 "regex": "(?im)^.*\\berror\\b.*$"
17287 }
17288 ],
17289 "file": {
17290 "path": "{{ inputs.path }}",
17291 "tail": "{{ inputs.lines }}"
17292 },
17293 "into": "log",
17294 "kind": "observe",
17295 "next": "exists"
17296 },
17297 "some": {
17298 "kind": "done",
17299 "output": {
17300 "errors": "{{ state.n }}",
17301 "lines": "{{ state.errors }}",
17302 "path": "{{ inputs.path }}"
17303 },
17304 "say": "{{ state.n }} error line(s) in the last {{ state.seen }} of {{ inputs.path }}; latest: {{ state.errors | last | strip }}"
17305 }
17306 },
17307 "paths": [
17308 "/var/log"
17309 ],
17310 "start": "read",
17311 "tags": [
17312 "ops",
17313 "box",
17314 "read-only"
17315 ],
17316 "triggers": [
17317 {
17318 "pattern": "errors in the log"
17319 },
17320 {
17321 "pattern": "errors in {path:word}"
17322 }
17323 ],
17324 "version": 2
17325 },
17326 "modem-check": {
17327 "description": "Whether a modem on a serial line answers AT.",
17328 "devices": [
17329 "/dev/ttyUSB*",
17330 "/dev/ttyACM*"
17331 ],
17332 "inputs": {
17333 "device": {
17334 "default": "/dev/ttyUSB0",
17335 "type": "str"
17336 }
17337 },
17338 "license": "MIT",
17339 "name": "modem-check",
17340 "namespace": "pyrtan",
17341 "nodes": {
17342 "answered": {
17343 "branches": [
17344 {
17345 "then": "yes",
17346 "when": {
17347 "eq": "OK"
17348 }
17349 },
17350 {
17351 "then": "error",
17352 "when": {
17353 "eq": "ERROR"
17354 }
17355 },
17356 {
17357 "else": "odd"
17358 }
17359 ],
17360 "kind": "decide",
17361 "on": "answer"
17362 },
17363 "at": {
17364 "extract": [
17365 {
17366 "into": "answer",
17367 "regex": "(OK|ERROR)"
17368 }
17369 ],
17370 "into": "s",
17371 "kind": "observe",
17372 "next": "answered",
17373 "retries": 1,
17374 "serial": {
17375 "baud": 9600,
17376 "device": "{{ inputs.device }}",
17377 "read_until": "\n",
17378 "write": "AT\r\n"
17379 },
17380 "timeout": 3
17381 },
17382 "error": {
17383 "kind": "fail",
17384 "reason": "modem-error",
17385 "say": "The modem on {{ inputs.device }} answered ERROR."
17386 },
17387 "odd": {
17388 "kind": "fail",
17389 "reason": "odd-answer",
17390 "say": "{{ inputs.device }} answered {{ state.s.body | strip }} to AT."
17391 },
17392 "yes": {
17393 "kind": "done",
17394 "output": {
17395 "device": "{{ inputs.device }}"
17396 },
17397 "say": "The modem on {{ inputs.device }} is there."
17398 }
17399 },
17400 "start": "at",
17401 "tags": [
17402 "hardware",
17403 "serial"
17404 ],
17405 "triggers": [
17406 {
17407 "pattern": "is the modem up"
17408 },
17409 {
17410 "pattern": "is the modem on {device:word} up"
17411 }
17412 ],
17413 "version": 2
17414 },
17415 "my-ip": {
17416 "description": "The public IP address requests from here come from, from ipify.",
17417 "hosts": [
17418 "api.ipify.org"
17419 ],
17420 "license": "MIT",
17421 "name": "my-ip",
17422 "namespace": "pyrtan",
17423 "nodes": {
17424 "fetch": {
17425 "extract": [
17426 {
17427 "into": "ip",
17428 "json": "json.ip"
17429 }
17430 ],
17431 "http": {
17432 "method": "GET",
17433 "url": "https://api.ipify.org?format=json"
17434 },
17435 "into": "resp",
17436 "kind": "observe",
17437 "next": "status"
17438 },
17439 "has-ip": {
17440 "branches": [
17441 {
17442 "then": "reply",
17443 "when": {
17444 "exists": true
17445 }
17446 },
17447 {
17448 "else": "unexpected"
17449 }
17450 ],
17451 "kind": "decide",
17452 "on": "ip"
17453 },
17454 "reply": {
17455 "kind": "done",
17456 "output": {
17457 "ip": "{{ state.ip }}"
17458 },
17459 "say": "Your public IP address is {{ state.ip }}."
17460 },
17461 "status": {
17462 "branches": [
17463 {
17464 "then": "has-ip",
17465 "when": {
17466 "eq": 200
17467 }
17468 },
17469 {
17470 "else": "unexpected"
17471 }
17472 ],
17473 "kind": "decide",
17474 "on": "resp.status"
17475 },
17476 "unexpected": {
17477 "kind": "fail",
17478 "reason": "unexpected-status",
17479 "say": "ipify answered {{ state.resp.status }}."
17480 }
17481 },
17482 "start": "fetch",
17483 "tags": [
17484 "network",
17485 "read-only"
17486 ],
17487 "triggers": [
17488 {
17489 "pattern": "what is my [public] ip [address]"
17490 },
17491 {
17492 "pattern": "my [public] ip [address]"
17493 }
17494 ],
17495 "version": 1
17496 },
17497 "next-launch": {
17498 "description": "The next rocket launch anywhere, when, and its status, from the Launch Library.",
17499 "hosts": [
17500 "ll.thespacedevs.com"
17501 ],
17502 "license": "MIT",
17503 "name": "next-launch",
17504 "namespace": "pyrtan",
17505 "nodes": {
17506 "any": {
17507 "branches": [
17508 {
17509 "then": "reply",
17510 "when": {
17511 "exists": true
17512 }
17513 },
17514 {
17515 "else": "none"
17516 }
17517 ],
17518 "kind": "decide",
17519 "on": "name"
17520 },
17521 "fetch": {
17522 "extract": [
17523 {
17524 "into": "name",
17525 "json": "json.results[0].name"
17526 },
17527 {
17528 "into": "net",
17529 "json": "json.results[0].net"
17530 },
17531 {
17532 "into": "state",
17533 "json": "json.results[0].status.name"
17534 },
17535 {
17536 "into": "count",
17537 "json": "json.count"
17538 }
17539 ],
17540 "http": {
17541 "method": "GET",
17542 "url": "https://ll.thespacedevs.com/2.3.0/launches/upcoming/?limit=1&mode=list&hide_recent_previous=true"
17543 },
17544 "into": "resp",
17545 "kind": "observe",
17546 "next": "status"
17547 },
17548 "none": {
17549 "kind": "done",
17550 "output": {
17551 "upcoming": 0
17552 },
17553 "say": "The Launch Library lists no upcoming launches."
17554 },
17555 "rate-limited": {
17556 "kind": "fail",
17557 "reason": "rate-limited",
17558 "say": "The Launch Library allows fifteen requests an hour without a key; try again later."
17559 },
17560 "reply": {
17561 "kind": "done",
17562 "output": {
17563 "name": "{{ state.name }}",
17564 "net_utc": "{{ state.net }}",
17565 "status": "{{ state.state }}",
17566 "upcoming": "{{ state.count }}"
17567 },
17568 "say": "Next launch: {{ state.name }}, {{ state.net }} UTC ({{ state.state }}). {{ state.count }} launches are scheduled."
17569 },
17570 "status": {
17571 "branches": [
17572 {
17573 "then": "any",
17574 "when": {
17575 "eq": 200
17576 }
17577 },
17578 {
17579 "then": "rate-limited",
17580 "when": {
17581 "eq": 429
17582 }
17583 },
17584 {
17585 "else": "unexpected"
17586 }
17587 ],
17588 "kind": "decide",
17589 "on": "resp.status"
17590 },
17591 "unexpected": {
17592 "kind": "fail",
17593 "reason": "unexpected-status",
17594 "say": "The Launch Library answered {{ state.resp.status }}."
17595 }
17596 },
17597 "start": "fetch",
17598 "tags": [
17599 "space",
17600 "read-only"
17601 ],
17602 "triggers": [
17603 {
17604 "pattern": "[when is the] next (launch|rocket launch)"
17605 },
17606 {
17607 "pattern": "upcoming (launches|rocket launches)"
17608 }
17609 ],
17610 "version": 1
17611 },
17612 "npm-package": {
17613 "description": "The latest version of an npm package, and what it says it is.",
17614 "hosts": [
17615 "registry.npmjs.org"
17616 ],
17617 "inputs": {
17618 "package": {
17619 "description": "A package name, as in: react or @types/node",
17620 "pattern": "^(@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*$",
17621 "type": "str"
17622 }
17623 },
17624 "license": "MIT",
17625 "name": "npm-package",
17626 "namespace": "pyrtan",
17627 "nodes": {
17628 "fetch": {
17629 "extract": [
17630 {
17631 "into": "version",
17632 "json": "json.version"
17633 },
17634 {
17635 "into": "about",
17636 "json": "json.description"
17637 }
17638 ],
17639 "http": {
17640 "method": "GET",
17641 "url": "https://registry.npmjs.org/{{ inputs.package | url }}/latest"
17642 },
17643 "into": "resp",
17644 "kind": "observe",
17645 "next": "status"
17646 },
17647 "no-package": {
17648 "kind": "fail",
17649 "reason": "no-package",
17650 "say": "npm has no package called {{ inputs.package }}."
17651 },
17652 "reply": {
17653 "kind": "done",
17654 "output": {
17655 "description": "{{ state.about }}",
17656 "package": "{{ inputs.package }}",
17657 "url": "https://www.npmjs.com/package/{{ inputs.package }}",
17658 "version": "{{ state.version }}"
17659 },
17660 "say": "{{ inputs.package }} {{ state.version }} on npm: {{ state.about }} https://www.npmjs.com/package/{{ inputs.package }}"
17661 },
17662 "status": {
17663 "branches": [
17664 {
17665 "then": "reply",
17666 "when": {
17667 "eq": 200
17668 }
17669 },
17670 {
17671 "then": "no-package",
17672 "when": {
17673 "eq": 404
17674 }
17675 },
17676 {
17677 "else": "unexpected"
17678 }
17679 ],
17680 "kind": "decide",
17681 "on": "resp.status"
17682 },
17683 "unexpected": {
17684 "kind": "fail",
17685 "reason": "unexpected-status",
17686 "say": "The npm registry answered {{ state.resp.status }}."
17687 }
17688 },
17689 "start": "fetch",
17690 "tags": [
17691 "software",
17692 "javascript",
17693 "read-only"
17694 ],
17695 "triggers": [
17696 {
17697 "pattern": "[what's the] latest version of {package:word} on npm"
17698 },
17699 {
17700 "pattern": "latest {package:word} on npm"
17701 },
17702 {
17703 "pattern": "npm (package|version) {package:word}"
17704 }
17705 ],
17706 "version": 1
17707 },
17708 "pokemon": {
17709 "description": "A Pokémon's number, types, height and weight, from PokéAPI.",
17710 "hosts": [
17711 "pokeapi.co"
17712 ],
17713 "inputs": {
17714 "name": {
17715 "description": "A Pokémon's name or number, as in: pikachu",
17716 "pattern": "^[A-Za-z0-9\\-]+$",
17717 "type": "str"
17718 }
17719 },
17720 "license": "MIT",
17721 "name": "pokemon",
17722 "namespace": "pyrtan",
17723 "nodes": {
17724 "fetch": {
17725 "extract": [
17726 {
17727 "into": "id",
17728 "json": "json.id"
17729 },
17730 {
17731 "into": "name",
17732 "json": "json.name"
17733 },
17734 {
17735 "all": true,
17736 "into": "types",
17737 "regex": "\"type\":\\s*\\{\\s*\"name\":\\s*\"([a-z]+)\""
17738 },
17739 {
17740 "into": "dm",
17741 "json": "json.height"
17742 },
17743 {
17744 "into": "hg",
17745 "json": "json.weight"
17746 }
17747 ],
17748 "http": {
17749 "method": "GET",
17750 "url": "https://pokeapi.co/api/v2/pokemon/{{ inputs.name | lower | url }}"
17751 },
17752 "into": "resp",
17753 "kind": "observe",
17754 "next": "status"
17755 },
17756 "no-pokemon": {
17757 "kind": "fail",
17758 "reason": "no-pokemon",
17759 "say": "PokéAPI knows no Pokémon called {{ inputs.name }}."
17760 },
17761 "reply": {
17762 "kind": "done",
17763 "output": {
17764 "height_dm": "{{ state.dm }}",
17765 "name": "{{ state.name }}",
17766 "number": "{{ state.id }}",
17767 "types": "{{ state.types }}",
17768 "weight_hg": "{{ state.hg }}"
17769 },
17770 "say": "#{{ state.id }} {{ state.name | title }}, {{ state.types | join }} type."
17771 },
17772 "status": {
17773 "branches": [
17774 {
17775 "then": "reply",
17776 "when": {
17777 "eq": 200
17778 }
17779 },
17780 {
17781 "then": "no-pokemon",
17782 "when": {
17783 "eq": 404
17784 }
17785 },
17786 {
17787 "else": "unexpected"
17788 }
17789 ],
17790 "kind": "decide",
17791 "on": "resp.status"
17792 },
17793 "unexpected": {
17794 "kind": "fail",
17795 "reason": "unexpected-status",
17796 "say": "PokéAPI answered {{ state.resp.status }}."
17797 }
17798 },
17799 "start": "fetch",
17800 "tags": [
17801 "fun",
17802 "games",
17803 "read-only"
17804 ],
17805 "triggers": [
17806 {
17807 "pattern": "pokemon {name:word}"
17808 },
17809 {
17810 "pattern": "what type is {name:word}"
17811 },
17812 {
17813 "pattern": "tell me about the pokemon {name:word}"
17814 }
17815 ],
17816 "version": 1
17817 },
17818 "postal-code": {
17819 "description": "The place a postal code belongs to, from Zippopotam.us (US ZIP codes, or a code in another country).",
17820 "hosts": [
17821 "api.zippopotam.us"
17822 ],
17823 "inputs": {
17824 "code": {
17825 "description": "A postal code, as in: 90210",
17826 "pattern": "^[A-Za-z0-9-]{2,10}$",
17827 "type": "str"
17828 },
17829 "country": {
17830 "default": "us",
17831 "description": "A two-letter country code, as in: US, DE, GB",
17832 "pattern": "^[A-Za-z]{2}$",
17833 "type": "str"
17834 }
17835 },
17836 "license": "MIT",
17837 "name": "postal-code",
17838 "namespace": "pyrtan",
17839 "nodes": {
17840 "fetch": {
17841 "extract": [
17842 {
17843 "into": "place",
17844 "json": "json.places[0][\"place name\"]"
17845 },
17846 {
17847 "into": "region",
17848 "json": "json.places[0].state"
17849 },
17850 {
17851 "into": "country_name",
17852 "json": "json.country"
17853 },
17854 {
17855 "into": "lat",
17856 "json": "json.places[0].latitude"
17857 },
17858 {
17859 "into": "lon",
17860 "json": "json.places[0].longitude"
17861 }
17862 ],
17863 "http": {
17864 "method": "GET",
17865 "url": "https://api.zippopotam.us/{{ inputs.country | lower }}/{{ inputs.code | url }}"
17866 },
17867 "into": "resp",
17868 "kind": "observe",
17869 "next": "status"
17870 },
17871 "reply": {
17872 "kind": "done",
17873 "output": {
17874 "code": "{{ inputs.code | upper }}",
17875 "country": "{{ state.country_name }}",
17876 "latitude": "{{ state.lat | float }}",
17877 "longitude": "{{ state.lon | float }}",
17878 "place": "{{ state.place }}",
17879 "region": "{{ state.region }}"
17880 },
17881 "say": "{{ inputs.code | upper }} is {{ state.place }}, {{ state.region }}, {{ state.country_name }}."
17882 },
17883 "status": {
17884 "branches": [
17885 {
17886 "then": "reply",
17887 "when": {
17888 "eq": 200
17889 }
17890 },
17891 {
17892 "then": "unknown-code",
17893 "when": {
17894 "eq": 404
17895 }
17896 },
17897 {
17898 "else": "unexpected"
17899 }
17900 ],
17901 "kind": "decide",
17902 "on": "resp.status"
17903 },
17904 "unexpected": {
17905 "kind": "fail",
17906 "reason": "unexpected-status",
17907 "say": "Zippopotam.us answered {{ state.resp.status }}."
17908 },
17909 "unknown-code": {
17910 "kind": "fail",
17911 "reason": "unknown-code",
17912 "say": "Zippopotam.us knows no postal code {{ inputs.code | upper }} in {{ inputs.country | upper }}."
17913 }
17914 },
17915 "start": "fetch",
17916 "tags": [
17917 "geography",
17918 "read-only"
17919 ],
17920 "triggers": [
17921 {
17922 "inputs": {
17923 "country": "us"
17924 },
17925 "pattern": "where is zip [code] {code:word}"
17926 },
17927 {
17928 "pattern": "[where is] (postcode|postal code) {code:word} in {country:word}"
17929 }
17930 ],
17931 "version": 1
17932 },
17933 "pypi-package": {
17934 "description": "The latest version of a Python package on PyPI, and what it says it is.",
17935 "hosts": [
17936 "pypi.org"
17937 ],
17938 "inputs": {
17939 "package": {
17940 "description": "A project name, as in: requests",
17941 "pattern": "^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$",
17942 "type": "str"
17943 }
17944 },
17945 "license": "MIT",
17946 "name": "pypi-package",
17947 "namespace": "pyrtan",
17948 "nodes": {
17949 "fetch": {
17950 "extract": [
17951 {
17952 "into": "name",
17953 "json": "json.info.name"
17954 },
17955 {
17956 "into": "version",
17957 "json": "json.info.version"
17958 },
17959 {
17960 "into": "about",
17961 "json": "json.info.summary"
17962 },
17963 {
17964 "into": "url",
17965 "json": "json.info.package_url"
17966 }
17967 ],
17968 "http": {
17969 "method": "GET",
17970 "url": "https://pypi.org/pypi/{{ inputs.package | url }}/json"
17971 },
17972 "into": "resp",
17973 "kind": "observe",
17974 "next": "status"
17975 },
17976 "no-package": {
17977 "kind": "fail",
17978 "reason": "no-package",
17979 "say": "PyPI has no project called {{ inputs.package }}."
17980 },
17981 "reply": {
17982 "kind": "done",
17983 "output": {
17984 "package": "{{ state.name }}",
17985 "summary": "{{ state.about }}",
17986 "url": "{{ state.url }}",
17987 "version": "{{ state.version }}"
17988 },
17989 "say": "{{ state.name }} {{ state.version }} on PyPI: {{ state.about }} {{ state.url }}"
17990 },
17991 "status": {
17992 "branches": [
17993 {
17994 "then": "reply",
17995 "when": {
17996 "eq": 200
17997 }
17998 },
17999 {
18000 "then": "no-package",
18001 "when": {
18002 "eq": 404
18003 }
18004 },
18005 {
18006 "else": "unexpected"
18007 }
18008 ],
18009 "kind": "decide",
18010 "on": "resp.status"
18011 },
18012 "unexpected": {
18013 "kind": "fail",
18014 "reason": "unexpected-status",
18015 "say": "PyPI answered {{ state.resp.status }}."
18016 }
18017 },
18018 "start": "fetch",
18019 "tags": [
18020 "software",
18021 "python",
18022 "read-only"
18023 ],
18024 "triggers": [
18025 {
18026 "pattern": "[what's the] latest version of {package:word} on pypi"
18027 },
18028 {
18029 "pattern": "latest {package:word} on pypi"
18030 },
18031 {
18032 "pattern": "(pypi|pip) (package|version) {package:word}"
18033 }
18034 ],
18035 "version": 1
18036 },
18037 "rendered-headline": {
18038 "description": "The main heading of a page that needs a script to render, through a real browser.",
18039 "hosts": [
18040 "*"
18041 ],
18042 "inputs": {
18043 "url": {
18044 "pattern": "^https?://.+$",
18045 "type": "str"
18046 }
18047 },
18048 "license": "MIT",
18049 "name": "rendered-headline",
18050 "namespace": "pyrtan",
18051 "nodes": {
18052 "found": {
18053 "branches": [
18054 {
18055 "then": "say",
18056 "when": {
18057 "exists": true
18058 }
18059 },
18060 {
18061 "else": "none"
18062 }
18063 ],
18064 "kind": "decide",
18065 "on": "heading"
18066 },
18067 "loaded": {
18068 "branches": [
18069 {
18070 "then": "found",
18071 "when": {
18072 "eq": true
18073 }
18074 },
18075 {
18076 "else": "not-ok"
18077 }
18078 ],
18079 "kind": "decide",
18080 "on": "page.ok"
18081 },
18082 "none": {
18083 "kind": "fail",
18084 "reason": "no-heading",
18085 "say": "{{ state.page.url }} rendered without an h1."
18086 },
18087 "not-ok": {
18088 "kind": "fail",
18089 "reason": "unexpected-response",
18090 "say": "{{ inputs.url }} answered {{ state.page.status }}."
18091 },
18092 "open": {
18093 "browser": {
18094 "url": "{{ inputs.url }}",
18095 "wait_for": "h1"
18096 },
18097 "extract": [
18098 {
18099 "css": "h1",
18100 "into": "heading"
18101 }
18102 ],
18103 "into": "page",
18104 "kind": "observe",
18105 "next": "loaded",
18106 "retries": 0,
18107 "timeout": 30
18108 },
18109 "say": {
18110 "kind": "done",
18111 "output": {
18112 "heading": "{{ state.heading | strip }}",
18113 "title": "{{ state.page.title }}",
18114 "url": "{{ state.page.url }}"
18115 },
18116 "say": "{{ state.page.title }} — {{ state.heading | strip }}"
18117 }
18118 },
18119 "start": "open",
18120 "tags": [
18121 "web",
18122 "browser",
18123 "read-only"
18124 ],
18125 "triggers": [
18126 {
18127 "pattern": "headline of {url:url}"
18128 },
18129 {
18130 "pattern": "what does {url:url} say"
18131 }
18132 ],
18133 "version": 2
18134 },
18135 "rhymes": {
18136 "description": "Words that rhyme with a word, from Datamuse.",
18137 "hosts": [
18138 "api.datamuse.com"
18139 ],
18140 "inputs": {
18141 "word": {
18142 "description": "One English word",
18143 "pattern": "^[A-Za-z][A-Za-z'\\-]*$",
18144 "type": "str"
18145 }
18146 },
18147 "license": "MIT",
18148 "name": "rhymes",
18149 "namespace": "pyrtan",
18150 "nodes": {
18151 "any": {
18152 "branches": [
18153 {
18154 "then": "none",
18155 "when": {
18156 "empty": true
18157 }
18158 },
18159 {
18160 "else": "reply"
18161 }
18162 ],
18163 "kind": "decide",
18164 "on": "words"
18165 },
18166 "fetch": {
18167 "extract": [
18168 {
18169 "all": true,
18170 "into": "words",
18171 "regex": "\"word\":\\s*\"([^\"]+)\""
18172 }
18173 ],
18174 "http": {
18175 "method": "GET",
18176 "url": "https://api.datamuse.com/words?rel_rhy={{ inputs.word | lower | url }}&max=10"
18177 },
18178 "into": "resp",
18179 "kind": "observe",
18180 "next": "status"
18181 },
18182 "none": {
18183 "kind": "fail",
18184 "reason": "no-rhymes",
18185 "say": "Datamuse knows nothing that rhymes with {{ inputs.word }}."
18186 },
18187 "reply": {
18188 "kind": "done",
18189 "output": {
18190 "rhymes": "{{ state.words }}",
18191 "word": "{{ inputs.word }}"
18192 },
18193 "say": "Rhymes with {{ inputs.word }}: {{ state.words | join }}."
18194 },
18195 "status": {
18196 "branches": [
18197 {
18198 "then": "any",
18199 "when": {
18200 "eq": 200
18201 }
18202 },
18203 {
18204 "else": "unexpected"
18205 }
18206 ],
18207 "kind": "decide",
18208 "on": "resp.status"
18209 },
18210 "unexpected": {
18211 "kind": "fail",
18212 "reason": "unexpected-status",
18213 "say": "Datamuse answered {{ state.resp.status }}."
18214 }
18215 },
18216 "start": "fetch",
18217 "tags": [
18218 "language",
18219 "read-only"
18220 ],
18221 "triggers": [
18222 {
18223 "pattern": "what rhymes with {word:word}"
18224 },
18225 {
18226 "pattern": "(rhymes|words that rhyme) (for|with) {word:word}"
18227 }
18228 ],
18229 "version": 1
18230 },
18231 "room-temperature": {
18232 "description": "The temperature a room's sensor last published, over MQTT.",
18233 "hosts": [
18234 "broker.local"
18235 ],
18236 "inputs": {
18237 "room": {
18238 "default": "kitchen",
18239 "pattern": "^[a-z0-9-]+$",
18240 "type": "str"
18241 }
18242 },
18243 "license": "MIT",
18244 "name": "room-temperature",
18245 "namespace": "pyrtan",
18246 "nodes": {
18247 "cold": {
18248 "kind": "done",
18249 "output": {
18250 "room": "{{ inputs.room }}",
18251 "temperature": "{{ state.q.json.temperature }}",
18252 "topic": "{{ state.q.message.topic }}"
18253 },
18254 "say": "{{ inputs.room }}: {{ state.q.json.temperature }}°C, cold."
18255 },
18256 "fine": {
18257 "kind": "done",
18258 "output": {
18259 "room": "{{ inputs.room }}",
18260 "temperature": "{{ state.q.json.temperature }}",
18261 "topic": "{{ state.q.message.topic }}"
18262 },
18263 "say": "{{ inputs.room }}: {{ state.q.json.temperature }}°C."
18264 },
18265 "got": {
18266 "branches": [
18267 {
18268 "then": "reading",
18269 "when": {
18270 "eq": true
18271 }
18272 },
18273 {
18274 "else": "quiet"
18275 }
18276 ],
18277 "kind": "decide",
18278 "on": "q.received"
18279 },
18280 "quiet": {
18281 "kind": "fail",
18282 "reason": "no-reading",
18283 "say": "Nothing on home/{{ inputs.room }}/temperature within 15 s; is the sensor publishing?"
18284 },
18285 "reading": {
18286 "branches": [
18287 {
18288 "then": "cold",
18289 "when": {
18290 "lt": 16
18291 }
18292 },
18293 {
18294 "then": "fine",
18295 "when": {
18296 "lt": 26
18297 }
18298 },
18299 {
18300 "then": "warm",
18301 "when": {
18302 "gte": 26
18303 }
18304 }
18305 ],
18306 "kind": "decide",
18307 "on": "q.json.temperature"
18308 },
18309 "wait": {
18310 "into": "q",
18311 "kind": "observe",
18312 "next": "got",
18313 "queue": {
18314 "topic": "home/{{ inputs.room }}/temperature",
18315 "url": "mqtt://broker.local"
18316 },
18317 "retries": 0,
18318 "timeout": 15
18319 },
18320 "warm": {
18321 "kind": "done",
18322 "output": {
18323 "room": "{{ inputs.room }}",
18324 "temperature": "{{ state.q.json.temperature }}",
18325 "topic": "{{ state.q.message.topic }}"
18326 },
18327 "say": "{{ inputs.room }}: {{ state.q.json.temperature }}°C, warm."
18328 }
18329 },
18330 "start": "wait",
18331 "tags": [
18332 "home",
18333 "mqtt"
18334 ],
18335 "topics": [
18336 "home/+/temperature"
18337 ],
18338 "triggers": [
18339 {
18340 "pattern": "[what's the] temperature in the {room:word}"
18341 },
18342 {
18343 "pattern": "how warm is the {room:word}"
18344 }
18345 ],
18346 "version": 2,
18347 "watches": [
18348 {
18349 "inputs": {
18350 "room": "kitchen"
18351 },
18352 "queue": {
18353 "topic": "home/kitchen/temperature",
18354 "url": "mqtt://broker.local"
18355 }
18356 }
18357 ]
18358 },
18359 "site-status": {
18360 "description": "Whether a web site answers, and how quickly.",
18361 "hosts": [
18362 "*"
18363 ],
18364 "inputs": {
18365 "host": {
18366 "description": "A host name, with no scheme",
18367 "pattern": "(?i)^[a-z0-9.\\-]+(:[0-9]+)?$",
18368 "type": "str"
18369 }
18370 },
18371 "license": "MIT",
18372 "name": "site-status",
18373 "namespace": "pyrtan",
18374 "nodes": {
18375 "erroring": {
18376 "kind": "fail",
18377 "output": {
18378 "host": "{{ inputs.host }}",
18379 "status": "{{ state.resp.status }}",
18380 "up": false
18381 },
18382 "reason": "server-error",
18383 "say": "{{ inputs.host }} is answering {{ state.resp.status }}: up, but broken."
18384 },
18385 "odd": {
18386 "kind": "fail",
18387 "reason": "unexpected-status",
18388 "say": "{{ inputs.host }} answered {{ state.resp.status }}, which this tree has no branch for."
18389 },
18390 "probe": {
18391 "http": {
18392 "method": "GET",
18393 "url": "https://{{ inputs.host | lower }}/"
18394 },
18395 "into": "resp",
18396 "kind": "observe",
18397 "next": "status",
18398 "retries": 1,
18399 "timeout": 8
18400 },
18401 "refusing": {
18402 "kind": "done",
18403 "output": {
18404 "host": "{{ inputs.host }}",
18405 "note": "client error on /",
18406 "status": "{{ state.resp.status }}",
18407 "up": true
18408 },
18409 "say": "{{ inputs.host }} answers but refuses the front page ({{ state.resp.status }}); the server is up."
18410 },
18411 "status": {
18412 "branches": [
18413 {
18414 "then": "up",
18415 "when": {
18416 "between": [
18417 200,
18418 399
18419 ]
18420 }
18421 },
18422 {
18423 "then": "refusing",
18424 "when": {
18425 "between": [
18426 400,
18427 499
18428 ]
18429 }
18430 },
18431 {
18432 "then": "erroring",
18433 "when": {
18434 "gte": 500
18435 }
18436 },
18437 {
18438 "else": "odd"
18439 }
18440 ],
18441 "kind": "decide",
18442 "on": "resp.status"
18443 },
18444 "up": {
18445 "kind": "done",
18446 "output": {
18447 "elapsed_ms": "{{ state.resp.elapsed_ms }}",
18448 "host": "{{ inputs.host }}",
18449 "status": "{{ state.resp.status }}",
18450 "up": true
18451 },
18452 "say": "{{ inputs.host }} is up: {{ state.resp.status }} in {{ state.resp.elapsed_ms }} ms."
18453 }
18454 },
18455 "start": "probe",
18456 "tags": [
18457 "ops",
18458 "read-only"
18459 ],
18460 "triggers": [
18461 {
18462 "pattern": "is {host:host} up"
18463 },
18464 {
18465 "pattern": "is {host:host} down"
18466 },
18467 {
18468 "pattern": "[check] status of {host:host}"
18469 }
18470 ],
18471 "version": 2,
18472 "watches": [
18473 {
18474 "every": "15m",
18475 "inputs": {
18476 "host": "example.com"
18477 },
18478 "name": "example.com"
18479 }
18480 ]
18481 },
18482 "small-talk": {
18483 "description": "How are you, who are you, what can you do, goodbye: what a stranger asks a bot in the chat before asking anything.",
18484 "inputs": {
18485 "topic": {
18486 "one_of": [
18487 "how",
18488 "who",
18489 "what",
18490 "bye"
18491 ],
18492 "type": "str"
18493 }
18494 },
18495 "license": "MIT",
18496 "name": "small-talk",
18497 "namespace": "pyrtan",
18498 "nodes": {
18499 "bye": {
18500 "kind": "done",
18501 "output": {
18502 "topic": "bye"
18503 },
18504 "say": "Bye. Press New to meet someone else."
18505 },
18506 "how": {
18507 "kind": "done",
18508 "output": {
18509 "topic": "how"
18510 },
18511 "say": "Deterministic, thanks: the same question takes the same path every time."
18512 },
18513 "topic": {
18514 "branches": [
18515 {
18516 "then": "how",
18517 "when": {
18518 "eq": "how"
18519 }
18520 },
18521 {
18522 "then": "who",
18523 "when": {
18524 "eq": "who"
18525 }
18526 },
18527 {
18528 "then": "what",
18529 "when": {
18530 "eq": "what"
18531 }
18532 },
18533 {
18534 "then": "bye",
18535 "when": {
18536 "eq": "bye"
18537 }
18538 }
18539 ],
18540 "kind": "decide",
18541 "on": "inputs.topic"
18542 },
18543 "what": {
18544 "kind": "done",
18545 "output": {
18546 "topic": "what"
18547 },
18548 "say": "Only what my trees were written for. Ask something else and I'll say that nobody answers it yet, and which tree would."
18549 },
18550 "who": {
18551 "kind": "done",
18552 "output": {
18553 "topic": "who"
18554 },
18555 "say": "A bot: a set of Pyrtan trees on this network, not a person and not a model. Every answer I give comes with the exact path through the tree that produced it."
18556 }
18557 },
18558 "start": "topic",
18559 "tags": [
18560 "chat",
18561 "read-only"
18562 ],
18563 "triggers": [
18564 {
18565 "inputs": {
18566 "topic": "how"
18567 },
18568 "pattern": "how are you [doing|today]"
18569 },
18570 {
18571 "inputs": {
18572 "topic": "who"
18573 },
18574 "pattern": "(who|what) are you"
18575 },
18576 {
18577 "inputs": {
18578 "topic": "what"
18579 },
18580 "pattern": "what (can|do) you do"
18581 },
18582 {
18583 "inputs": {
18584 "topic": "who"
18585 },
18586 "pattern": "are you (a bot|human|real|a person)"
18587 },
18588 {
18589 "inputs": {
18590 "topic": "bye"
18591 },
18592 "pattern": "(bye|goodbye|see you|see ya)"
18593 }
18594 ],
18595 "version": 1
18596 },
18597 "space-news": {
18598 "description": "The three latest spaceflight news articles, from the Spaceflight News API.",
18599 "hosts": [
18600 "api.spaceflightnewsapi.net"
18601 ],
18602 "license": "MIT",
18603 "name": "space-news",
18604 "namespace": "pyrtan",
18605 "nodes": {
18606 "any": {
18607 "branches": [
18608 {
18609 "then": "none",
18610 "when": {
18611 "empty": true
18612 }
18613 },
18614 {
18615 "else": "each"
18616 }
18617 ],
18618 "kind": "decide",
18619 "on": "articles"
18620 },
18621 "each": {
18622 "into": "stories",
18623 "kind": "each",
18624 "limit": 3,
18625 "next": "reply",
18626 "over": "articles",
18627 "start": "one"
18628 },
18629 "fetch": {
18630 "extract": [
18631 {
18632 "into": "articles",
18633 "json": "json.results"
18634 },
18635 {
18636 "into": "title",
18637 "json": "json.results[0].title"
18638 },
18639 {
18640 "into": "site",
18641 "json": "json.results[0].news_site"
18642 },
18643 {
18644 "into": "url",
18645 "json": "json.results[0].url"
18646 }
18647 ],
18648 "http": {
18649 "method": "GET",
18650 "url": "https://api.spaceflightnewsapi.net/v4/articles/?limit=3"
18651 },
18652 "into": "resp",
18653 "kind": "observe",
18654 "next": "status"
18655 },
18656 "none": {
18657 "kind": "done",
18658 "output": {
18659 "stories": []
18660 },
18661 "say": "The Spaceflight News API has no articles right now."
18662 },
18663 "one": {
18664 "kind": "done",
18665 "output": {
18666 "published": "{{ item.published_at }}",
18667 "site": "{{ item.news_site }}",
18668 "title": "{{ item.title }}",
18669 "url": "{{ item.url }}"
18670 }
18671 },
18672 "reply": {
18673 "kind": "done",
18674 "output": {
18675 "stories": "{{ state.stories }}"
18676 },
18677 "say": "Latest in space: {{ state.title }} ({{ state.site }}) {{ state.url }}"
18678 },
18679 "status": {
18680 "branches": [
18681 {
18682 "then": "any",
18683 "when": {
18684 "eq": 200
18685 }
18686 },
18687 {
18688 "else": "unexpected"
18689 }
18690 ],
18691 "kind": "decide",
18692 "on": "resp.status"
18693 },
18694 "unexpected": {
18695 "kind": "fail",
18696 "reason": "unexpected-status",
18697 "say": "The Spaceflight News API answered {{ state.resp.status }}."
18698 }
18699 },
18700 "start": "fetch",
18701 "tags": [
18702 "space",
18703 "news",
18704 "read-only"
18705 ],
18706 "triggers": [
18707 {
18708 "pattern": "[latest] (space|spaceflight) news"
18709 },
18710 {
18711 "pattern": "what's new in space"
18712 }
18713 ],
18714 "version": 1
18715 },
18716 "sun": {
18717 "description": "Today's sunrise and sunset for a place, in its local time, from Open-Meteo.",
18718 "hosts": [
18719 "geocoding-api.open-meteo.com",
18720 "api.open-meteo.com"
18721 ],
18722 "inputs": {
18723 "place": {
18724 "description": "A town or city name",
18725 "type": "str"
18726 }
18727 },
18728 "license": "MIT",
18729 "name": "sun",
18730 "namespace": "pyrtan",
18731 "nodes": {
18732 "found": {
18733 "branches": [
18734 {
18735 "then": "times",
18736 "when": {
18737 "exists": true
18738 }
18739 },
18740 {
18741 "else": "unknown-place"
18742 }
18743 ],
18744 "kind": "decide",
18745 "on": "hit"
18746 },
18747 "geocode": {
18748 "extract": [
18749 {
18750 "into": "hit",
18751 "json": "json.results[0]"
18752 },
18753 {
18754 "into": "lat",
18755 "json": "json.results[0].latitude"
18756 },
18757 {
18758 "into": "lon",
18759 "json": "json.results[0].longitude"
18760 },
18761 {
18762 "into": "name",
18763 "json": "json.results[0].name"
18764 },
18765 {
18766 "into": "country",
18767 "json": "json.results[0].country"
18768 }
18769 ],
18770 "http": {
18771 "method": "GET",
18772 "url": "https://geocoding-api.open-meteo.com/v1/search?name={{ inputs.place | url }}&count=1&language=en&format=json"
18773 },
18774 "into": "geo",
18775 "kind": "observe",
18776 "next": "found"
18777 },
18778 "have-times": {
18779 "branches": [
18780 {
18781 "then": "reply",
18782 "when": {
18783 "exists": true
18784 }
18785 },
18786 {
18787 "else": "no-times"
18788 }
18789 ],
18790 "kind": "decide",
18791 "on": "rise"
18792 },
18793 "no-times": {
18794 "kind": "fail",
18795 "reason": "no-times",
18796 "say": "Open-Meteo gave no sunrise for {{ state.name }} today (status {{ state.fc.status }}); near the poles the sun may not rise or set at all."
18797 },
18798 "reply": {
18799 "kind": "done",
18800 "output": {
18801 "country": "{{ state.country }}",
18802 "date": "{{ state.day }}",
18803 "place": "{{ state.name }}",
18804 "sunrise": "{{ state.rise }}",
18805 "sunset": "{{ state.set }}",
18806 "timezone": "{{ state.zone }}"
18807 },
18808 "say": "{{ state.name }}, {{ state.country }}, {{ state.day }}: sunrise {{ state.rise }}, sunset {{ state.set }} ({{ state.zone }})."
18809 },
18810 "times": {
18811 "extract": [
18812 {
18813 "into": "day",
18814 "json": "json.daily.time[0]"
18815 },
18816 {
18817 "from": "json.daily.sunrise[0]",
18818 "into": "rise",
18819 "regex": "T(\\d\\d:\\d\\d)"
18820 },
18821 {
18822 "from": "json.daily.sunset[0]",
18823 "into": "set",
18824 "regex": "T(\\d\\d:\\d\\d)"
18825 },
18826 {
18827 "into": "zone",
18828 "json": "json.timezone"
18829 }
18830 ],
18831 "http": {
18832 "method": "GET",
18833 "url": "https://api.open-meteo.com/v1/forecast?latitude={{ state.lat }}&longitude={{ state.lon }}&daily=sunrise,sunset&timezone=auto&forecast_days=1"
18834 },
18835 "into": "fc",
18836 "kind": "observe",
18837 "next": "have-times"
18838 },
18839 "unknown-place": {
18840 "kind": "fail",
18841 "reason": "unknown-place",
18842 "say": "I could not find a place called {{ inputs.place }}."
18843 }
18844 },
18845 "start": "geocode",
18846 "tags": [
18847 "weather",
18848 "read-only"
18849 ],
18850 "triggers": [
18851 {
18852 "pattern": "[when is] (sunrise|sunset) in {place}"
18853 },
18854 {
18855 "pattern": "when does the sun (rise|set) in {place}"
18856 },
18857 {
18858 "pattern": "(sunrise|sun) times (in|for) {place}"
18859 }
18860 ],
18861 "version": 1
18862 },
18863 "synonyms": {
18864 "description": "Synonyms for an English word, best first, from Datamuse.",
18865 "hosts": [
18866 "api.datamuse.com"
18867 ],
18868 "inputs": {
18869 "word": {
18870 "description": "One English word",
18871 "pattern": "^[A-Za-z][A-Za-z'\\-]*$",
18872 "type": "str"
18873 }
18874 },
18875 "license": "MIT",
18876 "name": "synonyms",
18877 "namespace": "pyrtan",
18878 "nodes": {
18879 "any": {
18880 "branches": [
18881 {
18882 "then": "none",
18883 "when": {
18884 "empty": true
18885 }
18886 },
18887 {
18888 "else": "reply"
18889 }
18890 ],
18891 "kind": "decide",
18892 "on": "words"
18893 },
18894 "fetch": {
18895 "extract": [
18896 {
18897 "all": true,
18898 "into": "words",
18899 "regex": "\"word\":\\s*\"([^\"]+)\""
18900 }
18901 ],
18902 "http": {
18903 "method": "GET",
18904 "url": "https://api.datamuse.com/words?rel_syn={{ inputs.word | lower | url }}&max=8"
18905 },
18906 "into": "resp",
18907 "kind": "observe",
18908 "next": "status"
18909 },
18910 "none": {
18911 "kind": "fail",
18912 "reason": "no-synonyms",
18913 "say": "Datamuse knows no synonyms for {{ inputs.word }}."
18914 },
18915 "reply": {
18916 "kind": "done",
18917 "output": {
18918 "synonyms": "{{ state.words }}",
18919 "word": "{{ inputs.word }}"
18920 },
18921 "say": "Synonyms for {{ inputs.word }}: {{ state.words | join }}."
18922 },
18923 "status": {
18924 "branches": [
18925 {
18926 "then": "any",
18927 "when": {
18928 "eq": 200
18929 }
18930 },
18931 {
18932 "else": "unexpected"
18933 }
18934 ],
18935 "kind": "decide",
18936 "on": "resp.status"
18937 },
18938 "unexpected": {
18939 "kind": "fail",
18940 "reason": "unexpected-status",
18941 "say": "Datamuse answered {{ state.resp.status }}."
18942 }
18943 },
18944 "start": "fetch",
18945 "tags": [
18946 "reference",
18947 "language",
18948 "read-only"
18949 ],
18950 "triggers": [
18951 {
18952 "pattern": "synonyms (for|of) {word:word}"
18953 },
18954 {
18955 "pattern": "(another|other) word for {word:word}"
18956 },
18957 {
18958 "pattern": "what is another word for {word:word}"
18959 }
18960 ],
18961 "version": 1
18962 },
18963 "uv-index": {
18964 "description": "The UV index for a place, now and today's highest, with its WHO band, from Open-Meteo.",
18965 "hosts": [
18966 "geocoding-api.open-meteo.com",
18967 "api.open-meteo.com"
18968 ],
18969 "inputs": {
18970 "place": {
18971 "description": "A town or city name",
18972 "type": "str"
18973 }
18974 },
18975 "license": "MIT",
18976 "name": "uv-index",
18977 "namespace": "pyrtan",
18978 "nodes": {
18979 "band": {
18980 "branches": [
18981 {
18982 "then": "low",
18983 "when": {
18984 "lt": 3,
18985 "type": "number"
18986 }
18987 },
18988 {
18989 "then": "moderate",
18990 "when": {
18991 "lt": 6
18992 }
18993 },
18994 {
18995 "then": "high",
18996 "when": {
18997 "lt": 8
18998 }
18999 },
19000 {
19001 "then": "very-high",
19002 "when": {
19003 "lt": 11
19004 }
19005 },
19006 {
19007 "then": "extreme",
19008 "when": {
19009 "gte": 11
19010 }
19011 },
19012 {
19013 "else": "no-reading"
19014 }
19015 ],
19016 "kind": "decide",
19017 "on": "max"
19018 },
19019 "extreme": {
19020 "kind": "set",
19021 "next": "reply",
19022 "set": {
19023 "advice": "stay out of the midday sun",
19024 "band": "extreme"
19025 }
19026 },
19027 "found": {
19028 "branches": [
19029 {
19030 "then": "uv",
19031 "when": {
19032 "exists": true
19033 }
19034 },
19035 {
19036 "else": "unknown-place"
19037 }
19038 ],
19039 "kind": "decide",
19040 "on": "hit"
19041 },
19042 "geocode": {
19043 "extract": [
19044 {
19045 "into": "hit",
19046 "json": "json.results[0]"
19047 },
19048 {
19049 "into": "lat",
19050 "json": "json.results[0].latitude"
19051 },
19052 {
19053 "into": "lon",
19054 "json": "json.results[0].longitude"
19055 },
19056 {
19057 "into": "name",
19058 "json": "json.results[0].name"
19059 },
19060 {
19061 "into": "country",
19062 "json": "json.results[0].country"
19063 },
19064 {
19065 "into": "zone",
19066 "json": "json.results[0].timezone"
19067 },
19068 {
19069 "into": "height",
19070 "json": "json.results[0].elevation"
19071 }
19072 ],
19073 "http": {
19074 "method": "GET",
19075 "url": "https://geocoding-api.open-meteo.com/v1/search?name={{ inputs.place | url }}&count=1&language=en&format=json"
19076 },
19077 "into": "geo",
19078 "kind": "observe",
19079 "next": "found"
19080 },
19081 "high": {
19082 "kind": "set",
19083 "next": "reply",
19084 "set": {
19085 "advice": "sunscreen, a hat, and shade at midday",
19086 "band": "high"
19087 }
19088 },
19089 "low": {
19090 "kind": "set",
19091 "next": "reply",
19092 "set": {
19093 "advice": "no protection needed",
19094 "band": "low"
19095 }
19096 },
19097 "moderate": {
19098 "kind": "set",
19099 "next": "reply",
19100 "set": {
19101 "advice": "sunscreen and a hat around midday",
19102 "band": "moderate"
19103 }
19104 },
19105 "no-reading": {
19106 "kind": "fail",
19107 "reason": "no-reading",
19108 "say": "Open-Meteo found {{ state.name }} but gave no UV index (status {{ state.fc.status }})."
19109 },
19110 "reply": {
19111 "kind": "done",
19112 "output": {
19113 "band": "{{ state.band }}",
19114 "country": "{{ state.country }}",
19115 "place": "{{ state.name }}",
19116 "uv_max": "{{ state.max }}",
19117 "uv_now": "{{ state.now }}"
19118 },
19119 "say": "{{ state.name }}, {{ state.country }}: UV index {{ state.now }} now, up to {{ state.max }} today ({{ state.band }}): {{ state.advice }}."
19120 },
19121 "unknown-place": {
19122 "kind": "fail",
19123 "reason": "unknown-place",
19124 "say": "I could not find a place called {{ inputs.place }}."
19125 },
19126 "uv": {
19127 "extract": [
19128 {
19129 "into": "now",
19130 "json": "json.current.uv_index"
19131 },
19132 {
19133 "into": "max",
19134 "json": "json.daily.uv_index_max[0]"
19135 }
19136 ],
19137 "http": {
19138 "method": "GET",
19139 "url": "https://api.open-meteo.com/v1/forecast?latitude={{ state.lat }}&longitude={{ state.lon }}&current=uv_index&daily=uv_index_max&timezone=auto&forecast_days=1"
19140 },
19141 "into": "fc",
19142 "kind": "observe",
19143 "next": "band"
19144 },
19145 "very-high": {
19146 "kind": "set",
19147 "next": "reply",
19148 "set": {
19149 "advice": "avoid the midday sun",
19150 "band": "very high"
19151 }
19152 }
19153 },
19154 "start": "geocode",
19155 "tags": [
19156 "weather",
19157 "health",
19158 "read-only"
19159 ],
19160 "triggers": [
19161 {
19162 "pattern": "[what's the] (uv|uv index) in {place}"
19163 },
19164 {
19165 "pattern": "do i need sunscreen in {place}"
19166 }
19167 ],
19168 "version": 1
19169 },
19170 "wayback": {
19171 "description": "The Wayback Machine's latest copy of a site, if it has one.",
19172 "hosts": [
19173 "archive.org"
19174 ],
19175 "inputs": {
19176 "site": {
19177 "description": "A host name, as in: example.com",
19178 "type": "str"
19179 }
19180 },
19181 "license": "MIT",
19182 "name": "wayback",
19183 "namespace": "pyrtan",
19184 "nodes": {
19185 "any": {
19186 "branches": [
19187 {
19188 "then": "reply",
19189 "when": {
19190 "exists": true
19191 }
19192 },
19193 {
19194 "else": "none"
19195 }
19196 ],
19197 "kind": "decide",
19198 "on": "copy"
19199 },
19200 "fetch": {
19201 "extract": [
19202 {
19203 "into": "copy",
19204 "json": "json.archived_snapshots.closest.url"
19205 },
19206 {
19207 "into": "stamp",
19208 "json": "json.archived_snapshots.closest.timestamp"
19209 }
19210 ],
19211 "http": {
19212 "method": "GET",
19213 "url": "https://archive.org/wayback/available?url={{ inputs.site | url }}"
19214 },
19215 "into": "resp",
19216 "kind": "observe",
19217 "next": "status"
19218 },
19219 "none": {
19220 "kind": "done",
19221 "output": {
19222 "site": "{{ inputs.site }}",
19223 "url": ""
19224 },
19225 "say": "The Wayback Machine has no copy of {{ inputs.site }}."
19226 },
19227 "reply": {
19228 "kind": "done",
19229 "output": {
19230 "site": "{{ inputs.site }}",
19231 "timestamp": "{{ state.stamp }}",
19232 "url": "{{ state.copy }}"
19233 },
19234 "say": "The Wayback Machine has {{ inputs.site }}. Its latest copy: {{ state.copy }}"
19235 },
19236 "status": {
19237 "branches": [
19238 {
19239 "then": "any",
19240 "when": {
19241 "eq": 200
19242 }
19243 },
19244 {
19245 "else": "unexpected"
19246 }
19247 ],
19248 "kind": "decide",
19249 "on": "resp.status"
19250 },
19251 "unexpected": {
19252 "kind": "fail",
19253 "reason": "unexpected-status",
19254 "say": "The Wayback Machine answered {{ state.resp.status }}."
19255 }
19256 },
19257 "start": "fetch",
19258 "tags": [
19259 "web",
19260 "read-only"
19261 ],
19262 "triggers": [
19263 {
19264 "pattern": "is {site:host} archived"
19265 },
19266 {
19267 "pattern": "wayback [machine] {site:host}"
19268 },
19269 {
19270 "pattern": "[the] archived copy of {site:host}"
19271 }
19272 ],
19273 "version": 1
19274 },
19275 "weather": {
19276 "description": "Current weather for a place, from Open-Meteo.",
19277 "hosts": [
19278 "geocoding-api.open-meteo.com",
19279 "api.open-meteo.com"
19280 ],
19281 "inputs": {
19282 "place": {
19283 "description": "A town or city name",
19284 "type": "str"
19285 },
19286 "units": {
19287 "default": "celsius",
19288 "one_of": [
19289 "celsius",
19290 "fahrenheit"
19291 ],
19292 "type": "str"
19293 }
19294 },
19295 "language": {
19296 "synonyms": {
19297 "weather": [
19298 "forecast",
19299 "temperature"
19300 ]
19301 }
19302 },
19303 "license": "MIT",
19304 "name": "weather",
19305 "namespace": "pyrtan",
19306 "nodes": {
19307 "clear": {
19308 "kind": "set",
19309 "next": "reply",
19310 "set": {
19311 "sky": "clear"
19312 }
19313 },
19314 "cloudy": {
19315 "kind": "set",
19316 "next": "reply",
19317 "set": {
19318 "sky": "cloudy"
19319 }
19320 },
19321 "fog": {
19322 "kind": "set",
19323 "next": "reply",
19324 "set": {
19325 "sky": "foggy"
19326 }
19327 },
19328 "forecast": {
19329 "extract": [
19330 {
19331 "into": "temp",
19332 "json": "json.current.temperature_2m"
19333 },
19334 {
19335 "into": "humidity",
19336 "json": "json.current.relative_humidity_2m"
19337 },
19338 {
19339 "into": "wind",
19340 "json": "json.current.wind_speed_10m"
19341 },
19342 {
19343 "into": "code",
19344 "json": "json.current.weather_code"
19345 },
19346 {
19347 "into": "unit",
19348 "json": "json.current_units.temperature_2m"
19349 }
19350 ],
19351 "http": {
19352 "method": "GET",
19353 "url": "https://api.open-meteo.com/v1/forecast?latitude={{ state.lat }}&longitude={{ state.lon }}&current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code&temperature_unit={{ inputs.units }}"
19354 },
19355 "into": "fc",
19356 "kind": "observe",
19357 "next": "have-temp"
19358 },
19359 "found": {
19360 "branches": [
19361 {
19362 "then": "forecast",
19363 "when": {
19364 "exists": true
19365 }
19366 },
19367 {
19368 "else": "unknown-place"
19369 }
19370 ],
19371 "kind": "decide",
19372 "on": "hit"
19373 },
19374 "geocode": {
19375 "extract": [
19376 {
19377 "into": "hit",
19378 "json": "json.results[0]"
19379 },
19380 {
19381 "into": "lat",
19382 "json": "json.results[0].latitude"
19383 },
19384 {
19385 "into": "lon",
19386 "json": "json.results[0].longitude"
19387 },
19388 {
19389 "into": "name",
19390 "json": "json.results[0].name"
19391 },
19392 {
19393 "into": "country",
19394 "json": "json.results[0].country"
19395 }
19396 ],
19397 "http": {
19398 "method": "GET",
19399 "url": "https://geocoding-api.open-meteo.com/v1/search?name={{ inputs.place | url }}&count=1&language=en&format=json"
19400 },
19401 "into": "geo",
19402 "kind": "observe",
19403 "next": "found"
19404 },
19405 "have-temp": {
19406 "branches": [
19407 {
19408 "then": "sky",
19409 "when": {
19410 "type": "number"
19411 }
19412 },
19413 {
19414 "else": "no-forecast"
19415 }
19416 ],
19417 "kind": "decide",
19418 "on": "temp"
19419 },
19420 "no-forecast": {
19421 "kind": "fail",
19422 "reason": "no-forecast",
19423 "say": "Open-Meteo found {{ state.name }} but returned no current temperature (status {{ state.fc.status }})."
19424 },
19425 "rain": {
19426 "kind": "set",
19427 "next": "reply",
19428 "set": {
19429 "sky": "raining"
19430 }
19431 },
19432 "reply": {
19433 "kind": "done",
19434 "output": {
19435 "country": "{{ state.country }}",
19436 "humidity": "{{ state.humidity }}",
19437 "place": "{{ state.name }}",
19438 "sky": "{{ state.sky }}",
19439 "temperature": "{{ state.temp }}",
19440 "unit": "{{ state.unit }}",
19441 "wind_kmh": "{{ state.wind }}"
19442 },
19443 "say": "{{ state.name }}, {{ state.country }}: {{ state.temp }}{{ state.unit }}, {{ state.sky }}, humidity {{ state.humidity }}%, wind {{ state.wind }} km/h."
19444 },
19445 "sky": {
19446 "branches": [
19447 {
19448 "then": "clear",
19449 "when": {
19450 "eq": 0
19451 }
19452 },
19453 {
19454 "then": "cloudy",
19455 "when": {
19456 "between": [
19457 1,
19458 3
19459 ]
19460 }
19461 },
19462 {
19463 "then": "fog",
19464 "when": {
19465 "between": [
19466 45,
19467 48
19468 ]
19469 }
19470 },
19471 {
19472 "then": "rain",
19473 "when": {
19474 "between": [
19475 51,
19476 67
19477 ]
19478 }
19479 },
19480 {
19481 "then": "snow",
19482 "when": {
19483 "between": [
19484 71,
19485 77
19486 ]
19487 }
19488 },
19489 {
19490 "then": "rain",
19491 "when": {
19492 "between": [
19493 80,
19494 82
19495 ]
19496 }
19497 },
19498 {
19499 "then": "snow",
19500 "when": {
19501 "between": [
19502 85,
19503 86
19504 ]
19505 }
19506 },
19507 {
19508 "then": "storm",
19509 "when": {
19510 "between": [
19511 95,
19512 99
19513 ]
19514 }
19515 },
19516 {
19517 "else": "unknown-sky"
19518 }
19519 ],
19520 "kind": "decide",
19521 "on": "code"
19522 },
19523 "snow": {
19524 "kind": "set",
19525 "next": "reply",
19526 "set": {
19527 "sky": "snowing"
19528 }
19529 },
19530 "storm": {
19531 "kind": "set",
19532 "next": "reply",
19533 "set": {
19534 "sky": "stormy"
19535 }
19536 },
19537 "unknown-place": {
19538 "kind": "fail",
19539 "reason": "unknown-place",
19540 "say": "I could not find a place called {{ inputs.place }}."
19541 },
19542 "unknown-sky": {
19543 "kind": "set",
19544 "next": "reply",
19545 "set": {
19546 "sky": "weather code {{ state.code }}"
19547 }
19548 }
19549 },
19550 "start": "geocode",
19551 "tags": [
19552 "weather",
19553 "read-only"
19554 ],
19555 "triggers": [
19556 {
19557 "pattern": "[what's the] weather in {place}"
19558 },
19559 {
19560 "pattern": "[what's the] weather for {place}"
19561 },
19562 {
19563 "pattern": "how [hot|cold|warm] is it in {place}"
19564 }
19565 ],
19566 "version": 2
19567 },
19568 "wikipedia": {
19569 "description": "The summary of a Wikipedia article.",
19570 "hosts": [
19571 "en.wikipedia.org"
19572 ],
19573 "inputs": {
19574 "topic": {
19575 "description": "An article title, as in: Ursula K. Le Guin",
19576 "type": "str"
19577 }
19578 },
19579 "license": "MIT",
19580 "name": "wikipedia",
19581 "namespace": "pyrtan",
19582 "nodes": {
19583 "ambiguous": {
19584 "kind": "fail",
19585 "reason": "ambiguous",
19586 "say": "{{ state.title }} could mean several things on Wikipedia; say which: {{ state.url }}"
19587 },
19588 "fetch": {
19589 "extract": [
19590 {
19591 "into": "kind",
19592 "json": "json.type"
19593 },
19594 {
19595 "into": "title",
19596 "json": "json.title"
19597 },
19598 {
19599 "into": "summary",
19600 "json": "json.extract"
19601 },
19602 {
19603 "into": "url",
19604 "json": "json.content_urls.desktop.page"
19605 }
19606 ],
19607 "http": {
19608 "method": "GET",
19609 "url": "https://en.wikipedia.org/api/rest_v1/page/summary/{{ inputs.topic | url }}"
19610 },
19611 "into": "resp",
19612 "kind": "observe",
19613 "next": "status"
19614 },
19615 "kind": {
19616 "branches": [
19617 {
19618 "then": "ambiguous",
19619 "when": {
19620 "eq": "disambiguation"
19621 }
19622 },
19623 {
19624 "then": "reply",
19625 "when": {
19626 "exists": true
19627 }
19628 },
19629 {
19630 "else": "unexpected"
19631 }
19632 ],
19633 "kind": "decide",
19634 "on": "kind"
19635 },
19636 "no-article": {
19637 "kind": "fail",
19638 "reason": "no-article",
19639 "say": "Wikipedia has no article called {{ inputs.topic }}."
19640 },
19641 "rate-limited": {
19642 "kind": "fail",
19643 "reason": "rate-limited",
19644 "say": "Wikipedia is asking for fewer requests (429); try again in a minute."
19645 },
19646 "reply": {
19647 "kind": "done",
19648 "output": {
19649 "summary": "{{ state.summary }}",
19650 "title": "{{ state.title }}",
19651 "url": "{{ state.url }}"
19652 },
19653 "say": "{{ state.title }}: {{ state.summary }} {{ state.url }}"
19654 },
19655 "status": {
19656 "branches": [
19657 {
19658 "then": "kind",
19659 "when": {
19660 "eq": 200
19661 }
19662 },
19663 {
19664 "then": "no-article",
19665 "when": {
19666 "eq": 404
19667 }
19668 },
19669 {
19670 "then": "rate-limited",
19671 "when": {
19672 "eq": 429
19673 }
19674 },
19675 {
19676 "else": "unexpected"
19677 }
19678 ],
19679 "kind": "decide",
19680 "on": "resp.status"
19681 },
19682 "unexpected": {
19683 "kind": "fail",
19684 "reason": "unexpected-status",
19685 "say": "Wikipedia answered {{ state.resp.status }}."
19686 }
19687 },
19688 "start": "fetch",
19689 "tags": [
19690 "reference",
19691 "read-only"
19692 ],
19693 "triggers": [
19694 {
19695 "pattern": "tell me about {topic}"
19696 },
19697 {
19698 "pattern": "who (is|was) {topic}"
19699 },
19700 {
19701 "pattern": "(wikipedia|wiki) {topic}"
19702 }
19703 ],
19704 "version": 1
19705 }
19706 }
19707}