mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-12 19:13:28 -10:00
* zeroline TUI: fix slash palette, token readout, @ files, ! bash
The zeroline home footer advertised affordances the code didn't keep. Fixes:
- '/' palette: autocomplete.go suppressed a bare '/' ('/' alone showed nothing). Now a bare '/' lists the full command palette (capped at 8). The footer's '/ commands' works.
- Token readout: the zeroline header was built without Cost/TotalTokens, so the bars always showed $0.00 and no token total. zerolineHeader() now threads m.usageTracker.Summary() (TotalCost + TotalTokens, with the unpriced-token fallback); the bottom bar shows a cumulative 'tok <N>' segment (zeroline.Header gains TotalTokens; humanTokens formats it).
- '@ files': '@' input now drives a workspace file picker in the existing overlay (trailingAtToken/fileSuggestions/replaceTrailingAtToken; bounded, skips .git/node_modules/vendor/dist/hidden). Selecting inserts the @path, preserving preceding prompt text. Previously '@…' was sent to the agent as a chat prompt.
- '! bash': '!cmd' now runs as a shell escape (commandBash) — executed async with a 30s timeout in the workspace, output shown in the transcript. Previously '!cmd' was sent to the agent as chat.
- '1-5 theme': left as-is — it works when the input is empty (the home screen's state); gating on empty input keeps digits typable in prompts.
Tests: bare-/ palette, @-token detection/replacement, file listing+filter+skip-dirs, !cmd parsing, header usage. build/vet/-race/full-suite + GOOS=windows green; no new deps.
* zeroline TUI: emit forward-slash @file paths (Windows fix)
fileSuggestions used filepath.Rel, which returns backslash paths on Windows, so the inserted @path token and the test assertions diverged (TestFileSuggestionsListsAndFiltersWorkspaceFiles failed on the Windows smoke). Normalize with filepath.ToSlash so @paths are forward-slash on every platform.
* zeroline TUI: gate !cmd behind unsafe mode + test humanTokens
CodeRabbit major: the !cmd shell escape ran bash -c directly with no permission check, bypassing the sandbox. It now runs ONLY in unsafe permission mode; in auto/ask it is not executed and the user is told to relaunch with --skip-permissions-unsafe. Adds TestBashEscapeGatedByPermissionMode (auto/ask gated, unsafe runs).
CodeRabbit minor: adds TestHumanTokens covering negative clamp, 0, sub-1k, 1k trim, fractional, and the 999999->1000k rounding edge.
* zeroline TUI: bound @file walk by entry count; humanTokens M-scale
Review fixes for #138:
- fileSuggestions now counts every WalkDir entry (directories included), not
just files, and stops at the budget; a directory-heavy tree could otherwise
be traversed in full each keystroke and stall the TUI. Extracted
fileSuggestionsBounded so the per-keystroke budget is unit-tested.
- humanTokens switches to "M" notation past 1,000,000 instead of rendering
"1000k"+ on large-context models; tests extended to the M boundaries.
* zeroline TUI: make unsafe mode reachable so the ! shell escape works
The "!cmd" shell escape is gated behind unsafe permission mode, but the
interactive TUI hardcoded Ask and shift+tab only cycles auto<->ask, so unsafe
was unreachable and the escape was dead code. Worse, the gate's own remediation
message ("Relaunch with --skip-permissions-unsafe") pointed at a flag the
interactive launch did not parse: "zero --skip-permissions-unsafe" fell through
to the unknown-command path.
Honor --skip-permissions-unsafe at interactive launch (both "zero" and
"zero zeroline"), mirroring exec, so unsafe mode -- and the ! escape -- is
reachable via an explicit opt-in. Thread the resolved mode through
runInteractiveTUI(WithSkin); the default stays Ask. Document the flag in
"zero --help" and add launch tests for both entrypoints.
* zeroline TUI: run the ! escape via the platform shell, not hardcoded bash
Now that --skip-permissions-unsafe makes the "!" escape reachable on every
platform, hardcoding "bash -c" left it broken on stock Windows (no bash on
PATH) and anywhere bash is not at a predictable path -- the advertised
"! bash" footer would just print an exec error.
Resolve the shell the same way the agent bash tool does: cmd.exe /d /s /c on
Windows, /bin/sh -c elsewhere. Add a platform test asserting the command is
wrapped correctly.
---------
Co-authored-by: KRATOS <[email protected]>
71 lines
2.0 KiB
Go
71 lines
2.0 KiB
Go
package tui
|
|
|
|
import (
|
|
"context"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
// bashEscapeTimeout bounds a "!cmd" shell escape so a hung command can't freeze
|
|
// the agent indefinitely.
|
|
const bashEscapeTimeout = 30 * time.Second
|
|
|
|
// bashResultMsg carries the combined output of a "!cmd" shell escape back to the
|
|
// model for display in the transcript.
|
|
type bashResultMsg struct {
|
|
command string
|
|
output string
|
|
}
|
|
|
|
// runBashEscape runs a user-typed "!cmd" in the workspace and returns its
|
|
// combined output as a message. The user typed it explicitly, so it runs
|
|
// directly (a deliberate shell escape, outside the agent sandbox), bounded by a
|
|
// timeout and executed asynchronously so the UI stays responsive.
|
|
func runBashEscape(cwd, command string) tea.Cmd {
|
|
return func() tea.Msg {
|
|
ctx, cancel := context.WithTimeout(context.Background(), bashEscapeTimeout)
|
|
defer cancel()
|
|
|
|
name, shellArgs := escapeShell(command)
|
|
cmd := exec.CommandContext(ctx, name, shellArgs...)
|
|
if strings.TrimSpace(cwd) != "" {
|
|
cmd.Dir = cwd
|
|
}
|
|
out, err := cmd.CombinedOutput()
|
|
text := strings.TrimRight(string(out), "\n")
|
|
|
|
switch {
|
|
case ctx.Err() == context.DeadlineExceeded:
|
|
text = appendNote(text, "[timed out after "+bashEscapeTimeout.String()+"]")
|
|
case err != nil:
|
|
text = appendNote(text, "[exit error: "+err.Error()+"]")
|
|
}
|
|
if strings.TrimSpace(text) == "" {
|
|
text = "(no output)"
|
|
}
|
|
return bashResultMsg{command: command, output: text}
|
|
}
|
|
}
|
|
|
|
// escapeShell returns the platform shell and arguments for a "!cmd" escape,
|
|
// matching the agent bash tool: cmd.exe on Windows, /bin/sh elsewhere. Hardcoding
|
|
// "bash" broke the escape on stock Windows (no bash on PATH) and anywhere bash is
|
|
// not installed at a predictable path.
|
|
func escapeShell(command string) (string, []string) {
|
|
if runtime.GOOS == "windows" {
|
|
return "cmd.exe", []string{"/d", "/s", "/c", command}
|
|
}
|
|
return "/bin/sh", []string{"-c", command}
|
|
}
|
|
|
|
func appendNote(text, note string) string {
|
|
if text == "" {
|
|
return note
|
|
}
|
|
return text + "\n" + note
|
|
}
|