4.7 KiB
G-015 Punk executable script subcommand: reliable non-interactive piped/script execution
Status: proposed Scope: src/vfs/_config/punk_main.tcl, src/lib/app-punkshell/punkshell.tcl (script path or a leaner dedicated app package) Acceptance: as in root GOALS.md index (canonical).
Context
Agents (and exec-style callers generally) currently have no reliable way to run a one-shot command against a punk executable. The working pattern is to pipe repl input into the default shell path, e.g. (powershell):
'dev projects.work *tomlish*;exit' | punk902z
This is fragile in two ways:
- The trailing
exitis load-bearing: without it, stdin EOF triggers the app-punkshell eof handling, whose default heuristic policy attempts to reopen the console (CONIN$//dev/tty) and drop into an interactive repl. When a console exists but no human is watching (typical agent/CI harness), that is a permanent hang.PUNK_PIPE_EOF=exitmitigates, but callers must know to set it. - If any command before the
exiterrors, the repl keeps consuming input and theexitline may never run as intended - the session can still land interactive, and the process exit code does not reflect the failure.
Meanwhile the script subcommand in the punk_main.tcl dispatch
(src/vfs/_config/punk_main.tcl ~1163) is an incomplete stub: it prints stderr
diagnostics ("main.tcl launching script with args: ...") and sets
::tcl_interactive, but never actually executes anything. Note also the dispatch
default (~1090): any first argument that is not a recognised mode/subcommand is
reclassified as script, so completing this path also completes the bare
<punkexe> <somefile> form.
The shell subcommand path (app-punkshell) wraps the session in shellfilter
channel stacks (punkshellout/punkshellerr with file/syslog logging targets,
plus ansiwrap on stderr in places). Those transforms exist for interactive-shell
ergonomics and logging; for a one-shot script call they add overhead, alter
output, and risk stderr chatter - which matters because Tcl exec treats any
stderr output as command failure by default (see the comment block in
punk_main.tcl's punk subcommand case).
Approach
<punkexe> script <file> [<args>...]- execute the file with conventional ::argv0/::argv semantics, exit with its status.... | <punkexe> script- read the whole of stdin as the script when no scriptname is supplied and stdin is piped/redirected; terminate at EOF, never reopening a console. The scriptname form must still work with piped stdin present (the script may itself want to read stdin) - "with or without a trailing scriptname".- No shellfilter stacks/transforms on the script path; plain channels.
- No launcher output of its own on stdout or stderr (remove the stub diagnostics); the script's own output is the only output.
- Errors: report on stderr, exit nonzero. Success: exit 0 (or the script's own explicit exit code). No interactive fallback on this subcommand under any eof/error condition - PUNK_PIPE_EOF and console availability are irrelevant here by design.
- The script interp loads the basic punk modules and aliases that are available
by default in an interactive punk shell, so commands like
dev projects.workwork in a piped one-liner with nopackage requireboilerplate. This is deliberately not the leanest environment for basic scripts - fast/bare script execution is the future province of an expandedtclshsubcommand, out of scope here. The exact recommended one-liner gets documented under G-017.
Alternatives considered
- Hardening the shell/repl pipe path instead (PUNK_PIPE_EOF, error-aborts-input)
- insufficient alone: the shell path's channel transforms and repl semantics are wrong for exec-style callers even when eof/error policy is fixed; a dedicated clean path is simpler than making the interactive path dual-purpose.
- Requiring agents to set PUNK_PIPE_EOF=exit and keep the trailing
exit- rejected as the durable answer: it leaves exit codes meaningless on mid-script errors and depends on every caller knowing the incantation. - A bare interp (no punk modules/aliases preloaded) for the script path -
rejected for this subcommand: the point is that shell-default commands
(
dev projects.worketc.) work as piped one-liners; lean bare-script execution belongs to a later expansion of thetclshsubcommand.
Notes
- The eof policy machinery (PUNK_PIPE_EOF exit/interactive/heuristic) in
app-punkshell (~punkshell.tcl 466-507) stays as-is for the
shellsubcommand; this goal does not change interactive-shell behaviour. - Related later goal: G-016 extends what the motivating
projects.workcall can find; G-017 documents the agent-facing invocation pattern once this lands.