main() body is now os.Exit(run(ctx, args, stdin, stdout, stderr))
where ctx comes from signal.NotifyContext and IO is io.Reader/Writer
instead of os.Std*. run() parses flags via a fresh flag.NewFlagSet
(no more package-level flag.* globals) and dispatches to:
runShowVersion(stdout)
runCheckUpdate(stdout, stderr)
runConvert(input, output, stdout, stderr)
runHeadless(ctx, opts)
runVerboseTable(ctx, opts)
runInteractive(ctx, opts)
Helpers extracted: parseFlags, resolveConfigPath, initLoggers,
configureStdlibLog, loadOrCreateConfig, buildRuntimeDeps,
makeHTTPLogSubscriber, shutdownManager. Signal-loop and fsnotify
watcher exit cleanly on ctx.Done() (previously chan-based with
unconditional os.Exit).
Behaviour preserved by smoke checks: -version, -update, generate
without --context, generate with bogus context, -check on
missing/valid config, -headless start+SIGINT shutdown.
Coverage: cmd/kportal 17.4% -> 71.3%. New run_test.go exercises
run() and every run* mode. main() and runInteractive remain at 0%
(TTY-required); not feasible without a tea.Program factory
abstraction, which would add complexity for minimal coverage gain.