README.md
June 24, 2026 · View on GitHub
███████╗██╗ ███████╗███████╗████████╗
██╔════╝██║ ██╔════╝██╔════╝╚══██╔══╝
█████╗ ██║ █████╗ █████╗ ██║
██╔══╝ ██║ ██╔══╝ ██╔══╝ ██║
██║ ███████╗███████╗███████╗ ██║
╚═╝ ╚══════╝╚══════╝╚══════╝ ╚═╝
[ p a r a l l e l e x p e c t ]
[EXPECT, BUT N SESSIONS AT ONCE]
"Tcl/Expect automated one terminal in 1990. This automates fifty, in parallel, from a playbook."
stryke-fleet is parallel expect/PTY automation as a pure-stryke package: transcripted sessions, declarative playbooks, a recipe corpus for the interactive CLIs everyone scripts by hand (ssh, sftp, telnet, sudo, su, passwd, psql, mysql, redis-cli, docker login, installers, network gear), and multi-host fan-out on stryke's thread pool. The PTY primitives (pty_spawn, pty_expect, pty_expect_table, …) and the parallelism (pmap) are stryke builtins — this package is the orchestration layer. No [ffi] table, no cdylib, no helper binary — just .stk modules loaded on use Fleet. Created by MenkeTechnologies.
strykelang · MenkeTechnologiesMeta · stryke-utils · stryke-mcpd
Read the Docs · Engineering Report
Table of Contents
- [0x00] Why a Package, Not Core
- [0x01] Install
- [0x02] Quick Start
- [0x03] Sublibraries
- [0x04] What's NOT in Here
- [0x05] CLI
- [0x06] Tests
- [0x07] Layout
- [0xFF] License
[0x00] Why a Package, Not Core
The spawn/expect/send loop is already core: pty_spawn, pty_send, pty_read, pty_expect, pty_expect_table, pty_buffer, pty_alive, pty_eof, pty_close, pty_interact are stryke builtins, and pmap gives every stryke program a thread pool for free. What core deliberately does NOT ship is opinion: what a "step" is, what a failed run returns, what an ssh login chain looks like, how a transcript is shaped. That's policy, and policy belongs in a package where it can version independently:
Fleet::Session— transcripted sessions. Every send, match, timeout, and close is recorded, so a failed run hands you the full interaction log instead of a stuck process.Fleet::Playbook— declarative step lists.+{expect, send, timeout, branches, optional}hashrefs run in order; first non-optional timeout fails the run with the step name.Fleet::Recipes— the corpus. Login chains and prompt dances as pure data generators — composable, splice-able, unit-testable without a target host.Fleet::Fanout— the part Tcl/Expect never had. One playbook, N targets, one PTY per thread, results in target order, wall-clock of the slowest host.
[0x01] Install
# From a release:
s pkg install -g github.com/MenkeTechnologies/stryke-fleet
# From a local checkout:
git clone https://github.com/MenkeTechnologies/stryke-fleet
cd stryke-fleet
s pkg install -g . # installs into ~/.stryke/store/stryke-fleet@<version>/
# Or via Makefile:
make install
No cargo step. No cdylib. The installed store directory contains only stryke.toml + lib/*.stk — no compiled artifacts, fully portable across platforms.
[0x02] Quick Start
use Fleet
# One session, transcripted
val $s = Fleet::Session::open("ssh user\@host")
Fleet::Session::expect($s, qr/password:/, 10)
Fleet::Session::send($s, "$pw\n")
Fleet::Session::close($s)
# Declarative playbook — first non-optional timeout fails the run
val $r = Fleet::Playbook::run("ssh user\@host", [
@{ Fleet::Recipes::ssh_login(+{ password => $pw }) },
+{ send => "uptime\n" },
+{ expect => qr/load average/, timeout => 15, name => "uptime output" },
+{ send => "exit\n" },
])
p $r->{ok} ? "done" : "failed: $r->{error}"
p "$_->{event} $_->{data}" for @{ $r->{transcript} }
# Branch tables — first match wins, optional action coderef
+{ branches => [
+{ re => qr/\(yes\/no\)\?/, do => fn { "yes\n" } },
+{ re => qr/password:/ },
],
send_matched => 1 }
# The headline: one playbook, fifty hosts, parallel PTY sessions
val $results = Fleet::Fanout::ssh([@hosts],
Fleet::Recipes::ssh_login(+{ password => $pw }))
val $part = Fleet::Fanout::partition($results)
p "ok: #{len @{$part->{ok}}} failed: #{len @{$part->{failed}}}"
[0x03] Sublibraries
| # | Module | File | Fns | Highlights |
|---|---|---|---|---|
| 1 | Fleet::Session | lib/Session.stk | 24 | open · send · send_line · send_lines (one line per send event) · send_secret (transmit but redact) · send_control (Ctrl-C/D/Z, pexpect sendcontrol) · expect · expect_exact (literal match, meta chars quoted) · expect_any (which of several prompts arrived) · sendline_expect (send-then-expect atom) · branch · read · drain (collect trailing output until idle) · buffer · alive · eof · interact · close · transcript · events · matches · last_match · timed_out · transcript_text |
| 2 | Fleet::Playbook | lib/Playbook.stk | 4 | validate · run (step lists with expect/send/branches/optional/send_matched/retries/retry_send/delay) · dry_run (pure step preview) · concat (explicit-form splice of step lists) |
| 3 | Fleet::Recipes | lib/Recipes.stk | 49 | ssh_login · ssh_copy_id · sudo · doas · su · psql · mysql · mariadb · pg_dump · redis_cli · mongo · influx · yes_to_all · cisco_enable · telnet_login · sftp · scp · ftp_login · passwd_change · docker_login · npm_login · gpg_decrypt · openssl_pem · rsync · ssh_keygen · git_clone · kinit · ldapsearch · restic · borg · age · smbclient · vault_login · aws_configure · htpasswd · keytool · ansible_vault · sqlplus · op_signin · ssh_add · unzip_encrypted · cqlsh · cryptsetup · clickhouse_client · gcloud_auth · heroku_login · pass_insert · bw_unlock · names |
| 4 | Fleet::Fanout | lib/Fanout.stk | 18 | run (N targets, pmap) · batch (bounded concurrency, K at a time) · ssh (host-list convenience) · partition · count (total/ok/failed tally) · all_ok (boolean fleet-pass gate) · failed_cmds (cmd strings of failures) · ok_cmds (cmd strings of successes) · find (first result by cmd pattern) · pluck (project one key per result) · pluck_ok (project one key per success) · summarize (counts + error lines) · group_by (generic bucketing by a key fn) · group_by_error (bucket failures by error) · group_by_output (bucket successes by output — config-drift) · grep_transcript (which hosts printed X) · retry_failed (re-run only the failures) · retry_until (re-run failures up to N times) |
Recipes are pure data — they return playbook arrayrefs and never spawn anything, so they compose with @{...} splices and unit-test without a network.
[0x04] What's NOT in Here
By design — these are stryke builtins, so we don't re-wrap them:
| Category | Builtins (call directly) |
|---|---|
| PTY primitives | pty_spawn · pty_send · pty_read · pty_expect · pty_expect_table · pty_buffer · pty_alive · pty_eof · pty_close · pty_interact |
| Parallelism | pmap · pgrep · ppool and the rest of the parallel suite |
| Remote dispatch | cluster([...]) + pmap_on — persistent SSH worker pools for non-interactive remote compute; Fleet is for sessions that prompt back |
| Method-form sugar | PtyHandle class (require "perl_pty_class.stk" from strykelang examples) |
If a function in this library can be replaced with one builtin call, it's a bug.
[0x05] CLI
s bin/fleet.stk expect "sh -c 'sleep 1; echo ready'" ready 10 # spawn, wait, print match
s bin/fleet.stk exchange cat hello hello 5 # spawn, send, wait
s bin/fleet.stk recipes # list the recipe corpus
s bin/fleet.stk version
s bin/fleet.stk help
bin/fleet.stk covers one-shot expect/send loops from the shell. Anything with branches, retries, or more than two steps belongs in a playbook script — see examples/.
[0x06] Tests
s test t/ # assertions across every public function
t/test_fleet.stk is headless-CI safe by contract: every PTY assertion drives a local cat/sh — no network, no SSH, no target hosts (enforced by tests/repo-contract.sh). Recipes are asserted structurally as data.
[0x07] Layout
stryke-fleet/
├── stryke.toml # pure-stryke package manifest (no [ffi])
├── Makefile # test / install / clean
├── LICENSE # MIT
├── lib/
│ ├── Fleet.stk # `use Fleet` — pulls all four sublibs
│ ├── Session.stk # `use Fleet::Session` — transcripted PTY sessions
│ ├── Playbook.stk # `use Fleet::Playbook` — declarative step runner
│ ├── Recipes.stk # `use Fleet::Recipes` — login/prompt corpus
│ └── Fanout.stk # `use Fleet::Fanout` — parallel multi-target runs
├── bin/
│ └── fleet.stk # CLI front-end (one-shot expect/exchange)
├── t/
│ └── test_fleet.stk # all-surface assertions (local processes only)
├── examples/
│ ├── local_demo.stk # runnable anywhere — drives a local sh
│ ├── installer_autopilot.stk # yes_to_all against a fake installer
│ ├── parallel_ssh.stk # the headline: recipe × N hosts, partitioned
│ └── orchestrate.stk # all four layers: recipe + playbook + transcript + fanout
├── tests/ # shell gate scripts (CI lints)
└── docs/ # GitHub Pages site
[0xFF] License
MIT — see LICENSE.