ajhahn.de
← eeco
Go 61 lines
// Package tui is eeco's control center: a hybrid command box with a
// live status digest, rendered inline in the terminal scrollback (no
// alt-screen takeover). It accepts slash commands (with history and Tab
// completion) and free-text requests; free text is routed through the
// shared, gated AI provider so it is consented, budget-capped, and
// parked-and-queued rather than ever a silent spend or a hard failure.
//
// The control center only orchestrates engine operations that already
// exist (config, memory, garbage collection, queue, workflow run and
// scaffold). It introduces no new write path, obeys write-scope, and
// honours the same exit-code contract as every other entry point.
//
// When stdout or stdin is not a terminal (piped or CI) there is no
// interactive loop: Run prints the one-screen status digest and exits 0.
package tui

import (
	"fmt"
	"io"
	"os"

	"github.com/ajhahnde/eeco/internal/config"
	tea "github.com/charmbracelet/bubbletea"
)

// interactive reports whether an interactive control center should be
// started. Both stdin and stdout must be character devices; a pipe, a
// file, or a CI sink is not, so tests and pipelines deterministically
// take the digest path. The check is stdlib-only (no extra dependency).
func interactive() bool {
	return isCharDevice(os.Stdin) && isCharDevice(os.Stdout)
}

func isCharDevice(f *os.File) bool {
	if f == nil {
		return false
	}
	info, err := f.Stat()
	if err != nil {
		return false
	}
	return info.Mode()&os.ModeCharDevice != 0
}

// Run is the control-center entry point invoked by `eeco` with no
// arguments. Non-interactive: write the one-screen digest to stdout and
// return 0. Interactive: run the inline control center on the real
// terminal; return 0 on a clean quit, 1 on a terminal I/O failure.
func Run(cfg *config.Config, version string, stdout, stderr io.Writer) int {
	if !interactive() {
		fmt.Fprint(stdout, OneScreen(cfg, version))
		return 0
	}
	p := tea.NewProgram(newModel(cfg, version))
	if _, err := p.Run(); err != nil {
		fmt.Fprintln(stderr, "eeco:", err)
		return 1
	}
	return 0
}