agent-session-kill 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ import { decorateEntry } from "./format.js";
2
+
3
+ const DEFAULT_PAGE_SIZE = 20;
4
+ const INTENT_NONE = "none";
5
+
6
+ function normalizeNumber(value) {
7
+ return Number.isFinite(value) ? value : 0;
8
+ }
9
+
10
+ function reclaimableSize(row) {
11
+ return row.selectable ? normalizeNumber(row.sizeBytes) : 0;
12
+ }
13
+
14
+ function totalSize(row) {
15
+ return normalizeNumber(row.sizeBytes);
16
+ }
17
+
18
+ function matchesFilter(row, filter) {
19
+ if (!filter) return true;
20
+
21
+ const haystack = `${row.tool ?? ""} ${row.category ?? ""} ${row.path ?? ""}`.toLowerCase();
22
+ return haystack.includes(filter);
23
+ }
24
+
25
+ function compareRows(a, b) {
26
+ return (
27
+ reclaimableSize(b) - reclaimableSize(a) ||
28
+ totalSize(b) - totalSize(a) ||
29
+ String(a.path ?? "").localeCompare(String(b.path ?? ""))
30
+ );
31
+ }
32
+
33
+ function clampCursor(state) {
34
+ const max = Math.max(0, visibleRows(state).length - 1);
35
+ const nextCursor = Math.min(Math.max(0, state.cursor), max);
36
+ if (nextCursor === state.cursor) return state;
37
+ return { ...state, cursor: nextCursor };
38
+ }
39
+
40
+ export function createTuiState(entries) {
41
+ return {
42
+ entries: Array.isArray(entries) ? entries.map(decorateEntry) : [],
43
+ cursor: 0,
44
+ selected: new Set(),
45
+ filter: "",
46
+ intent: INTENT_NONE,
47
+ pageSize: DEFAULT_PAGE_SIZE,
48
+ };
49
+ }
50
+
51
+ export function visibleRows(state) {
52
+ const filter = String(state.filter ?? "").trim().toLowerCase();
53
+
54
+ return state.entries.filter((row) => matchesFilter(row, filter)).slice().sort(compareRows);
55
+ }
56
+
57
+ export function selectedRows(state) {
58
+ return state.entries.filter((row) => row.selectable && state.selected.has(row.path));
59
+ }
60
+
61
+ export function handleTuiKey(state, key = {}) {
62
+ const rows = visibleRows(state);
63
+ const current = rows[state.cursor];
64
+ const name = key.name;
65
+ let next = { ...state, intent: INTENT_NONE };
66
+
67
+ if (name === "up" || name === "k") {
68
+ next.cursor -= 1;
69
+ } else if (name === "down" || name === "j") {
70
+ next.cursor += 1;
71
+ } else if (name === "pageup" || (key.ctrl && name === "u")) {
72
+ next.cursor -= state.pageSize;
73
+ } else if (name === "pagedown" || (key.ctrl && name === "d")) {
74
+ next.cursor += state.pageSize;
75
+ } else if (name === "home") {
76
+ next.cursor = 0;
77
+ } else if (name === "end") {
78
+ next.cursor = rows.length - 1;
79
+ } else if ((name === "space" || key.sequence === " ") && current?.selectable) {
80
+ const selected = new Set(state.selected);
81
+
82
+ if (selected.has(current.path)) {
83
+ selected.delete(current.path);
84
+ } else {
85
+ selected.add(current.path);
86
+ }
87
+
88
+ next.selected = selected;
89
+ } else if (name === "a") {
90
+ const visibleSelectable = rows.filter((row) => row.selectable);
91
+ const allSelected = visibleSelectable.length > 0 && visibleSelectable.every((row) => state.selected.has(row.path));
92
+ const selected = new Set(state.selected);
93
+
94
+ for (const row of visibleSelectable) {
95
+ if (allSelected) {
96
+ selected.delete(row.path);
97
+ } else {
98
+ selected.add(row.path);
99
+ }
100
+ }
101
+
102
+ next.selected = selected;
103
+ } else if (name === "d" || name === "delete") {
104
+ next.intent = "delete-selected";
105
+ } else if (name === "r") {
106
+ next.intent = "rescan";
107
+ } else if (name === "o") {
108
+ next.intent = "open-current";
109
+ } else if (name === "q" || name === "escape") {
110
+ next.intent = "quit";
111
+ }
112
+
113
+ return clampCursor(next);
114
+ }