imake
An interactive make companion, two tools in one binary:

imake — a TUI that lists your Makefile targets; pick one and run it.
imake <group> — a k9s-inspired task runner: define named command groups in imake.yml, run them concurrently, watch their logs side by side, and click or press Enter to drop into any task's shell.
Install
Quick install — one script for macOS, Linux, and Windows (Git Bash),
no Go needed:
curl -fsSL https://raw.githubusercontent.com/gshireesh/imake/main/install.sh | sh
On Windows it installs to %LOCALAPPDATA%\Programs\imake and adds it
to your user PATH; native PowerShell users can use instead:
irm https://raw.githubusercontent.com/gshireesh/imake/main/install.ps1 | iex
Windows tasks run through cmd.exe on ConPTY. Prebuilt binaries cover
macOS, Linux and Windows on amd64 and arm64.
With Go: go install github.com/gshireesh/imake/cmd/imake@latest · from a checkout:
make install.
Once installed, imake -u (or --update) self-updates to the latest
release, imake -v prints the version, and imake -h shows usage.
The TUI also checks for a new release in the background (at most once
a day) and downloads it quietly — the header shows a restart hint and
the next launch runs the new version. Set IMAKE_NO_AUTOUPDATE=1 to
disable.
Task runner
Create an imake.yml (or imake.yaml) describing named command groups. A task is either a plain command string or an object with lifecycle hooks:
dev:
ui: cd discover && pnpm dev
backend:
command: cd backend && go run cmd/main.go
before: echo "starting backend..."
timeout: 10000 # ms; kills the command if it runs longer
on_timeout: echo "timed out"
on_error: echo "failed"
on_success: echo "ok"
after: echo "done"
restart_policy: on-failure # always | on-failure | never (default)
keep_shell: true # drop into an interactive shell after the command
Then run the group:
imake dev
Each task runs on its own pty behind a real terminal emulator
(midterm), so full-screen child TUIs —
turbo, vite, vim, htop, fancy shell prompts — render correctly instead of
turning into escape-code soup. The left panel lists tasks with live status
(● running, ❯ interactive shell, ✓ ok, ✗ failed, ■ stopped); the
right panel shows the selected task's live screen. Scrolling up switches the
pane to a line-based history buffer (copy mode); b or scrolling to the
bottom returns to the live screen.
Keys & mouse
| Input |
Action |
↑/↓, j/k, wheel over sidebar |
move selection |
←/h, →/l |
collapse / expand the group under the cursor |
| click task row |
select task (click a group header to toggle it) |
Enter, i, click log panel |
attach — keys go to the task's shell |
/ |
search: filters the task list from the sidebar, or log lines while attached |
Esc |
clear the active filter, then detach / leave fullscreen |
Esc, Ctrl+\ |
detach |
wheel over logs, u/d, U/D, t/b |
scroll / page / top / bottom |
f |
full-screen the task panel (esc returns) |
w |
toggle line wrapping in copy mode |
c |
copy the task's output (the filtered lines when / is active) |
| mouse drag over logs |
select rows (highlighted) and copy them on release |
m |
release the mouse to the terminal for native selection (press again to recapture) |
r |
run / restart the selected task with a cleared panel |
q, x |
stop the selected task |
Ctrl+C |
quit (stops all tasks) |
With keep_shell: true the task ends in a real interactive shell ($SHELL),
so attaching gives you a prompt in that task's context — handy for rerunning
dev servers, inspecting state, or poking at a failed build.
Lifecycle
Per run, a task executes: before → command (with optional timeout) →
on_success/on_error/on_timeout → after → optional keep_shell shell.
restart_policy: always reruns the cycle when it ends; on-failure reruns
only after a failure or timeout.
Tasks sharing a group are shown under a collapsible ▾ group header, and
tasks are nested under the dependency they wait for with tree connectors
(├──/└──), so the sidebar reads like tree output.
Tasks with manual: true show as ▷ and wait until you press r.
Tasks with depends_on show as ◌ and start automatically once every
dependency has finished with success (manual tasks always need r).
Dependency cycles and unknown names are rejected at startup.
Plain mode
imake -p <group> skips the TUI and streams all tasks with name-prefixed
output — useful for CI logs. Lifecycle hooks other than command are ignored
in plain mode.
See examples/simple, examples/lifecycle,
examples/manual and examples/grouped
for configs.
More demos
Attach to a task's shell — a manual task with keep_shell, started
with r, then real commands typed straight into it:

Search everywhere — / filters the task list from the sidebar and log
lines from log mode, k9s-style, with the prompt inside the panel being
searched:

Groups, dependencies and manual tasks — collapsible ▾ group headers,
tree-style dependency nesting, and ▷ manual tasks started on demand:

Viewing modes — LOG MODE and ATTACHED badges, f fullscreen,
w wrap toggle, c copy to clipboard, q stop and r fresh restart:

Group picker and Makefile browser — bare imake picks a group
interactively; imake . browses Makefile targets:

Bare imake
With an imake.yml present, bare imake opens a group picker — arrow keys
or click, Enter opens the group's task TUI — so nobody has to remember
group names (a single-group config opens directly; piped output prints the
plain list). Without one, a Makefile opens the target browser.
Makefile TUI
Run imake . (or bare imake when only a Makefile exists): arrow keys
to select a target, Enter to run it, Ctrl-C to quit. Target docs (text
after ##) show in the help pane.
Release flow
New tags publish as pre-releases: artifacts exist for testing, but
auto-update, imake -u and the install scripts only serve the release
marked latest. After verifying a build, promote it:
make promote TAG=vX.Y.Z # or: GitHub → release → uncheck "pre-release", set as latest
Rollback works the same way — mark an older release as latest and
updaters converge back to it.
Debugging
Set IMAKE_DEBUG=/tmp/imake.log to write key/message traces while the task
runner TUI is open.