diff --git a/CHANGELOG.md b/CHANGELOG.md index c694b9e8..1b448606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.4.1] - 2026-07-07 + +- `script` subcommand now matches `tclsh` for GUI scripts: if the script leaves a registered Tk main loop (Tk loaded with a live main window), the event loop is serviced until the last window closes, instead of sourcing-then-exiting immediately. So a Tk script with no explicit `exit` (or one that exits from an `after` callback, like `scriptlib/tktimer.tcl`) stays alive and runs — previously it flashed and died, and `shell` was needed as a workaround (relevant to G-020 GUI automation). Console scripts are unaffected; a script that errors exits at once (no hanging window). Verified on both generations. + ## [0.4.0] - 2026-07-07 - `script` subcommand supports `lib:` scriptlib scripts (with or without `.tcl` extension), matching the `shell` subcommand's scheme: `punkexe script lib:hello`, or bare `punkexe lib:hello` via reclassification. The prefix always wins — a literal path beginning `lib:` (pathological; illegal on Windows filesystems) is reachable via `./lib:...`. Not-found errors list the searched locations. Only `.tcl` runs via the script subcommand. diff --git a/goals/G-020-screencap-input-module.md b/goals/G-020-screencap-input-module.md index bb09293c..451b7c53 100644 --- a/goals/G-020-screencap-input-module.md +++ b/goals/G-020-screencap-input-module.md @@ -83,6 +83,11 @@ the module is a separate development, not a refactor of it. ## Notes +- Launch path (2026-07-07): the `script` subcommand (G-015) services a + registered Tk main loop after the script, matching tclsh - so a Tk app can + be launched via ` script app.tcl` and stays alive to be snapshotted + and driven, without needing the `shell` subcommand. This is the intended + entry point for GUI automation here. - Related: G-018 (kit composition; Tk as loadable package, no wish binaries), G-019 (a trimmed capture-capable executable is a plausible scan target), G-021 (agent-facing surface over this module), G-001 (backend plugin pattern). diff --git a/punkproject.toml b/punkproject.toml index 949c835c..890d81c5 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,3 +1,3 @@ [project] name = "punkshell" -version = "0.4.0" +version = "0.4.1" diff --git a/src/lib/AGENTS.md b/src/lib/AGENTS.md index da4f65a2..e879c437 100644 --- a/src/lib/AGENTS.md +++ b/src/lib/AGENTS.md @@ -16,7 +16,7 @@ Source of truth for all editable `pkgIndex.tcl`-based library packages. These ar - Libraries here include: - `app-punk/` — Punk REPL app entry (`app-punk::repl`) - `app-punkshell/` — Punkshell app entry (`app-punkshell`); on eof of its input channel the `env(PUNK_PIPE_EOF)` policy (`exit`|`interactive`|unset=console heuristic) decides between terminating and reopening the console for an interactive repl — automated callers should set `exit` or end piped input with an explicit `exit` - - `app-punkscript/` — lean one-shot script runner behind the punk executable `script` subcommand (G-015): runs a file, a `lib:` scriptlib script (resolved via `punk::path::scriptlib_resolve`), or piped stdin in the default punk shell module/alias environment with honest exit codes; no shellfilter stacks or logging side effects, never falls into an interactive shell — the reliable path for automated/agent callers (prefer this over piping into `shell`); stdin form echoes the script's final result when non-empty + - `app-punkscript/` — lean one-shot script runner behind the punk executable `script` subcommand (G-015): runs a file, a `lib:` scriptlib script (resolved via `punk::path::scriptlib_resolve`), or piped stdin in the default punk shell module/alias environment with honest exit codes; no shellfilter stacks or logging side effects, never falls into an interactive shell — the reliable path for automated/agent callers (prefer this over piping into `shell`); stdin form echoes the script's final result when non-empty; GUI parity with `tclsh` — a script leaving a registered Tk main loop is serviced until its window closes (so `script app.tcl` keeps a Tk app alive), while console scripts and errored scripts exit immediately - `app-shellspy/` — ShellSpy app entry (`app-shellspy`) - `app_shell/` — Shell app helpers (`app_shell`) - `app_shellrun/` — Shell run helpers (`app_shellrun`) diff --git a/src/lib/app-punkscript/punkscript.tcl b/src/lib/app-punkscript/punkscript.tcl index ac7aea04..fe29bf4c 100644 --- a/src/lib/app-punkscript/punkscript.tcl +++ b/src/lib/app-punkscript/punkscript.tcl @@ -16,6 +16,12 @@ package provide app-punkscript 1.0 # output (and, on error, the error report to stderr) appears. # - exit codes: 0 on success, 1 on script error or usage error; a script's own # explicit exit code is honoured. +# - GUI parity with tclsh: if the script leaves a registered Tk main loop +# (loaded Tk with a live main window), the event loop is serviced until the +# last main window closes, exactly as tclsh's Tcl_Main does - so a Tk script +# with no explicit exit stays alive (needed for GUI/G-020 use) rather than +# flashing and dying. A pure console script has no main loop and exits at +# once; a script error exits at once (no hanging window). # #punk_main.tcl sets ::argv to the arguments after the 'script' subcommand #before requiring this package (a bare non-subcommand first argument is also @@ -32,6 +38,23 @@ apply {{} { expr {[dict exists $conf -inputmode] || [dict exists $conf -mode]} }}] + #Match tclsh: after the script body, if a Tk main loop is registered (Tk + #loaded with a live main window '.'), service the event loop until '.' is + #destroyed - the script-level equivalent of Tcl_Main running a registered + #main loop. Then exit with $code. A script that exits from a callback (e.g. + #tktimer at end of countdown) never returns from tkwait - its exit wins. + #Only the success paths call this; errors exit immediately above. + set finish [apply {{} { + return {{code} { + if {[info exists ::tk_version] && [winfo exists .]} { + catch {tkwait window .} + } + flush stdout + flush stderr + exit $code + }} + }}] + #default punk shell environment via shared definitions - no local alias lists package require punk package require punk::aliascore @@ -94,9 +117,7 @@ apply {{} { flush stderr exit 1 } - flush stdout - flush stderr - exit 0 + apply $finish 0 } else { #stdin form: read the whole of piped/redirected stdin as the script. #An interactive terminal with no scriptname is a usage error - never @@ -126,8 +147,6 @@ apply {{} { if {[string length $result]} { puts stdout $result } - flush stdout - flush stderr - exit 0 + apply $finish 0 } }} diff --git a/src/vfs/_vfscommon.vfs/lib/app-punkscript/punkscript.tcl b/src/vfs/_vfscommon.vfs/lib/app-punkscript/punkscript.tcl index ac7aea04..fe29bf4c 100644 --- a/src/vfs/_vfscommon.vfs/lib/app-punkscript/punkscript.tcl +++ b/src/vfs/_vfscommon.vfs/lib/app-punkscript/punkscript.tcl @@ -16,6 +16,12 @@ package provide app-punkscript 1.0 # output (and, on error, the error report to stderr) appears. # - exit codes: 0 on success, 1 on script error or usage error; a script's own # explicit exit code is honoured. +# - GUI parity with tclsh: if the script leaves a registered Tk main loop +# (loaded Tk with a live main window), the event loop is serviced until the +# last main window closes, exactly as tclsh's Tcl_Main does - so a Tk script +# with no explicit exit stays alive (needed for GUI/G-020 use) rather than +# flashing and dying. A pure console script has no main loop and exits at +# once; a script error exits at once (no hanging window). # #punk_main.tcl sets ::argv to the arguments after the 'script' subcommand #before requiring this package (a bare non-subcommand first argument is also @@ -32,6 +38,23 @@ apply {{} { expr {[dict exists $conf -inputmode] || [dict exists $conf -mode]} }}] + #Match tclsh: after the script body, if a Tk main loop is registered (Tk + #loaded with a live main window '.'), service the event loop until '.' is + #destroyed - the script-level equivalent of Tcl_Main running a registered + #main loop. Then exit with $code. A script that exits from a callback (e.g. + #tktimer at end of countdown) never returns from tkwait - its exit wins. + #Only the success paths call this; errors exit immediately above. + set finish [apply {{} { + return {{code} { + if {[info exists ::tk_version] && [winfo exists .]} { + catch {tkwait window .} + } + flush stdout + flush stderr + exit $code + }} + }}] + #default punk shell environment via shared definitions - no local alias lists package require punk package require punk::aliascore @@ -94,9 +117,7 @@ apply {{} { flush stderr exit 1 } - flush stdout - flush stderr - exit 0 + apply $finish 0 } else { #stdin form: read the whole of piped/redirected stdin as the script. #An interactive terminal with no scriptname is a usage error - never @@ -126,8 +147,6 @@ apply {{} { if {[string length $result]} { puts stdout $result } - flush stdout - flush stderr - exit 0 + apply $finish 0 } }}