detached 2470fda

Show human-readable session prune sizes

commit
2470fdad980c2aa3e363b42cb5a3bf709c2de1aa
author
patriceckhart · · 2026-08-24 05:16 UTC
parent
9b7bb6a
signature
unsigned
refs
tag: v0.3.49
3 files · +27 -3
M README.md +1 -1
@@ -430,7 +430,7 @@ This is a guardrail against accidents, not a hard security boundary. If you need
430430
431431 Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy.
432432
433-Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups.
433+Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number and human-readable total size of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups.
434434
435435 Use `--older-than` to prune sessions by last activity instead, including sessions for directories that still exist. Supported age units are `m` (minutes), `h` (hours), `d` (24-hour days), `w` (7-day weeks), `mo` (calendar months), and `y` (calendar years). Add `--cwd PATH` to limit age pruning to one working directory. The session file is checked again immediately before deletion and preserved if it has become active recently.
436436
@@ -430,7 +430,7 @@ This is a guardrail against accidents, not a hard security boundary. If you need
430 430
431 Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy.431 Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy.
432 432
433-Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups.433+Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number and human-readable total size of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups.
434 434
435 Use `--older-than` to prune sessions by last activity instead, including sessions for directories that still exist. Supported age units are `m` (minutes), `h` (hours), `d` (24-hour days), `w` (7-day weeks), `mo` (calendar months), and `y` (calendar years). Add `--cwd PATH` to limit age pruning to one working directory. The session file is checked again immediately before deletion and preserved if it has become active recently.435 Use `--older-than` to prune sessions by last activity instead, including sessions for directories that still exist. Supported age units are `m` (minutes), `h` (hours), `d` (24-hour days), `w` (7-day weeks), `mo` (calendar months), and `y` (calendar years). Add `--cwd PATH` to limit age pruning to one working directory. The session file is checked again immediately before deletion and preserved if it has become active recently.
436 436
M packages/agent/sessionscmd.go +2 -1
@@ -14,6 +14,7 @@ import (
1414 "time"
1515
1616 "github.com/patriceckhart/zot/packages/core"
17+ "github.com/patriceckhart/zot/packages/provider"
1718 )
1819
1920 type sessionsPruneOptions struct {
@@ -266,7 +267,7 @@ func runSessionsPrune(opts sessionsPruneOptions, in io.Reader, out, errOut io.Wr
266267 fmt.Fprintln(out, "missing session directories:")
267268 }
268269 for idx, group := range candidates {
269- fmt.Fprintf(out, " %d. %s (%s, %d bytes)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), group.SizeBytes)
270+ fmt.Fprintf(out, " %d. %s (%s, %s)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), provider.FormatBytes(group.SizeBytes))
270271 }
271272 if opts.dryRun {
272273 fmt.Fprintf(out, "dry run: %s in %s would be deleted\n", sessionCount(totalSessions(candidates)), directoryCount(len(candidates)))
@@ -14,6 +14,7 @@ import (
14 "time"14 "time"
15 15
16 "github.com/patriceckhart/zot/packages/core"16 "github.com/patriceckhart/zot/packages/core"
17+ "github.com/patriceckhart/zot/packages/provider"
17 )18 )
18 19
19 type sessionsPruneOptions struct {20 type sessionsPruneOptions struct {
@@ -266,7 +267,7 @@ func runSessionsPrune(opts sessionsPruneOptions, in io.Reader, out, errOut io.Wr
266 fmt.Fprintln(out, "missing session directories:")267 fmt.Fprintln(out, "missing session directories:")
267 }268 }
268 for idx, group := range candidates {269 for idx, group := range candidates {
269- fmt.Fprintf(out, " %d. %s (%s, %d bytes)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), group.SizeBytes)270+ fmt.Fprintf(out, " %d. %s (%s, %s)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), provider.FormatBytes(group.SizeBytes))
270 }271 }
271 if opts.dryRun {272 if opts.dryRun {
272 fmt.Fprintf(out, "dry run: %s in %s would be deleted\n", sessionCount(totalSessions(candidates)), directoryCount(len(candidates)))273 fmt.Fprintf(out, "dry run: %s in %s would be deleted\n", sessionCount(totalSessions(candidates)), directoryCount(len(candidates)))
M packages/agent/sessionscmd_test.go +24 -1
@@ -107,6 +107,24 @@ func TestSessionsPruneDryRunPreservesSessions(t *testing.T) {
107107 }
108108 }
109109
110+func TestSessionsPruneDisplaysHumanReadableGroupSizes(t *testing.T) {
111+ home := t.TempDir()
112+ t.Setenv("ZOT_HOME", home)
113+ missing := filepath.Join(home, "missing-project")
114+ createPruneTestSessionWithText(t, home, missing, strings.Repeat("a", 2048))
115+
116+ var out, errOut bytes.Buffer
117+ if err := runSessionsPrune(sessionsPruneOptions{dryRun: true}, strings.NewReader(""), &out, &errOut, os.Stat); err != nil {
118+ t.Fatal(err)
119+ }
120+ if !strings.Contains(out.String(), "KiB") {
121+ t.Fatalf("output = %q, want binary size unit", out.String())
122+ }
123+ if strings.Contains(out.String(), " bytes)") {
124+ t.Fatalf("output = %q, want human-readable size instead of raw bytes", out.String())
125+ }
126+}
127+
110128 func TestSessionsPruneInteractiveDeletesSelectedGroup(t *testing.T) {
111129 home := t.TempDir()
112130 t.Setenv("ZOT_HOME", home)
@@ -285,6 +303,11 @@ func TestParseSessionsPruneOptions(t *testing.T) {
285303 }
286304
287305 func createPruneTestSession(t *testing.T, root, cwd string) string {
306+ t.Helper()
307+ return createPruneTestSessionWithText(t, root, cwd, "hello")
308+}
309+
310+func createPruneTestSessionWithText(t *testing.T, root, cwd, text string) string {
288311 t.Helper()
289312 session, err := core.NewSession(root, cwd, "test", "test-model", "test-version")
290313 if err != nil {
@@ -292,7 +315,7 @@ func createPruneTestSession(t *testing.T, root, cwd string) string {
292315 }
293316 if err := session.AppendMessage(provider.Message{
294317 Role: provider.RoleUser,
295- Content: []provider.Content{provider.TextBlock{Text: "hello"}},
318+ Content: []provider.Content{provider.TextBlock{Text: text}},
296319 }); err != nil {
297320 t.Fatal(err)
298321 }
@@ -107,6 +107,24 @@ func TestSessionsPruneDryRunPreservesSessions(t *testing.T) {
107 }107 }
108 }108 }
109 109
110+func TestSessionsPruneDisplaysHumanReadableGroupSizes(t *testing.T) {
111+ home := t.TempDir()
112+ t.Setenv("ZOT_HOME", home)
113+ missing := filepath.Join(home, "missing-project")
114+ createPruneTestSessionWithText(t, home, missing, strings.Repeat("a", 2048))
115+
116+ var out, errOut bytes.Buffer
117+ if err := runSessionsPrune(sessionsPruneOptions{dryRun: true}, strings.NewReader(""), &out, &errOut, os.Stat); err != nil {
118+ t.Fatal(err)
119+ }
120+ if !strings.Contains(out.String(), "KiB") {
121+ t.Fatalf("output = %q, want binary size unit", out.String())
122+ }
123+ if strings.Contains(out.String(), " bytes)") {
124+ t.Fatalf("output = %q, want human-readable size instead of raw bytes", out.String())
125+ }
126+}
127+
110 func TestSessionsPruneInteractiveDeletesSelectedGroup(t *testing.T) {128 func TestSessionsPruneInteractiveDeletesSelectedGroup(t *testing.T) {
111 home := t.TempDir()129 home := t.TempDir()
112 t.Setenv("ZOT_HOME", home)130 t.Setenv("ZOT_HOME", home)
@@ -285,6 +303,11 @@ func TestParseSessionsPruneOptions(t *testing.T) {
285 }303 }
286 304
287 func createPruneTestSession(t *testing.T, root, cwd string) string {305 func createPruneTestSession(t *testing.T, root, cwd string) string {
306+ t.Helper()
307+ return createPruneTestSessionWithText(t, root, cwd, "hello")
308+}
309+
310+func createPruneTestSessionWithText(t *testing.T, root, cwd, text string) string {
288 t.Helper()311 t.Helper()
289 session, err := core.NewSession(root, cwd, "test", "test-model", "test-version")312 session, err := core.NewSession(root, cwd, "test", "test-model", "test-version")
290 if err != nil {313 if err != nil {
@@ -292,7 +315,7 @@ func createPruneTestSession(t *testing.T, root, cwd string) string {
292 }315 }
293 if err := session.AppendMessage(provider.Message{316 if err := session.AppendMessage(provider.Message{
294 Role: provider.RoleUser,317 Role: provider.RoleUser,
295- Content: []provider.Content{provider.TextBlock{Text: "hello"}},318+ Content: []provider.Content{provider.TextBlock{Text: text}},
296 }); err != nil {319 }); err != nil {
297 t.Fatal(err)320 t.Fatal(err)
298 }321 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close