sessioner 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.
- package/README.md +251 -0
- package/lib/actions/delete.d.ts +4 -0
- package/lib/actions/delete.js +57 -0
- package/lib/actions/fork.d.ts +2 -0
- package/lib/actions/fork.js +30 -0
- package/lib/actions/merge.d.ts +2 -0
- package/lib/actions/merge.js +106 -0
- package/lib/actions/open.d.ts +2 -0
- package/lib/actions/open.js +10 -0
- package/lib/actions/preview.d.ts +2 -0
- package/lib/actions/preview.js +56 -0
- package/lib/actions/prune.d.ts +2 -0
- package/lib/actions/prune.js +103 -0
- package/lib/actions/rename.d.ts +2 -0
- package/lib/actions/rename.js +87 -0
- package/lib/actions/trim.d.ts +2 -0
- package/lib/actions/trim.js +26 -0
- package/lib/bin/index.d.ts +2 -0
- package/lib/bin/index.js +217 -0
- package/lib/helpers/ansi.d.ts +9 -0
- package/lib/helpers/ansi.js +9 -0
- package/lib/helpers/format-date.d.ts +1 -0
- package/lib/helpers/format-date.js +8 -0
- package/lib/helpers/format-stats.d.ts +3 -0
- package/lib/helpers/format-stats.js +81 -0
- package/lib/helpers/get-columns.d.ts +1 -0
- package/lib/helpers/get-columns.js +8 -0
- package/lib/helpers/rename-cache.d.ts +3 -0
- package/lib/helpers/rename-cache.js +18 -0
- package/lib/helpers/styled-label.d.ts +2 -0
- package/lib/helpers/styled-label.js +2 -0
- package/lib/helpers/truncate.d.ts +1 -0
- package/lib/helpers/truncate.js +1 -0
- package/lib/sessions/analyze-prune.d.ts +12 -0
- package/lib/sessions/analyze-prune.js +77 -0
- package/lib/sessions/count-subagents.d.ts +2 -0
- package/lib/sessions/count-subagents.js +13 -0
- package/lib/sessions/extract-first-user-message.d.ts +1 -0
- package/lib/sessions/extract-first-user-message.js +42 -0
- package/lib/sessions/extract-messages.d.ts +5 -0
- package/lib/sessions/extract-messages.js +43 -0
- package/lib/sessions/get-entries.d.ts +1 -0
- package/lib/sessions/get-entries.js +25 -0
- package/lib/sessions/list-sessions.d.ts +2 -0
- package/lib/sessions/list-sessions.js +29 -0
- package/lib/sessions/parse-line.d.ts +2 -0
- package/lib/sessions/parse-line.js +8 -0
- package/lib/sessions/rewrite-jsonl.d.ts +1 -0
- package/lib/sessions/rewrite-jsonl.js +34 -0
- package/lib/sessions/stats.d.ts +3 -0
- package/lib/sessions/stats.js +146 -0
- package/lib/types.d.ts +100 -0
- package/lib/types.js +1 -0
- package/lib/ui/custom-select.d.ts +2 -0
- package/lib/ui/custom-select.js +113 -0
- package/lib/ui/select.d.ts +10 -0
- package/lib/ui/select.js +459 -0
- package/package.json +64 -0
package/lib/ui/select.js
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { isCancel, text } from '@clack/prompts';
|
|
2
|
+
import { dim } from '../helpers/ansi.js';
|
|
3
|
+
import { getColumns } from '../helpers/get-columns.js';
|
|
4
|
+
import { truncate } from '../helpers/truncate.js';
|
|
5
|
+
import { select as customSelect } from './custom-select.js';
|
|
6
|
+
const PAGE_SIZE = 10;
|
|
7
|
+
const NEXT = -1;
|
|
8
|
+
const PREVIOUS = -2;
|
|
9
|
+
const EXIT = -3;
|
|
10
|
+
const CONFIRM = -4;
|
|
11
|
+
const CANCEL = -5;
|
|
12
|
+
const HEADER_MARGIN = 13; // terminal right-side safety margin
|
|
13
|
+
const CLACK_PREFIX = 6; // @clack/prompts left gutter width
|
|
14
|
+
const CHECKBOX_WIDTH = 2; // "☑ " / "☐ " prefix
|
|
15
|
+
const EXIT_NUMBER = 0;
|
|
16
|
+
const CANCEL_NUMBER = 0;
|
|
17
|
+
const buildShortcuts = (options) => {
|
|
18
|
+
const shortcuts = new Map();
|
|
19
|
+
for (let index = 0; index < options.length; index++) {
|
|
20
|
+
const option = options[index];
|
|
21
|
+
if (!option || option.number === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
shortcuts.set(String(option.number), index);
|
|
24
|
+
}
|
|
25
|
+
return shortcuts;
|
|
26
|
+
};
|
|
27
|
+
const clear = () => {
|
|
28
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
29
|
+
};
|
|
30
|
+
const printHeader = (fields) => {
|
|
31
|
+
clear();
|
|
32
|
+
const columns = getColumns();
|
|
33
|
+
const longest = Math.max(...Object.keys(fields).map((key) => key.length));
|
|
34
|
+
console.log();
|
|
35
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
36
|
+
const padding = ' '.repeat(longest - key.length + 2);
|
|
37
|
+
const prefixLength = 2 + key.length + 1 + padding.length;
|
|
38
|
+
const available = columns - prefixLength - HEADER_MARGIN;
|
|
39
|
+
const truncated = available > 0 ? truncate(value, available) : value;
|
|
40
|
+
console.log(` ${dim(`${key}:`)}${padding}${truncated}`);
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
};
|
|
44
|
+
const showHeader = (fields) => {
|
|
45
|
+
if (!fields)
|
|
46
|
+
return clear();
|
|
47
|
+
printHeader(fields);
|
|
48
|
+
};
|
|
49
|
+
export const selectMain = async (project, base, emptyCount = 0) => {
|
|
50
|
+
printHeader({ Project: project, Base: base });
|
|
51
|
+
const options = [
|
|
52
|
+
{ label: 'Sessions', value: 'sessions' },
|
|
53
|
+
{ label: 'Stats', value: 'stats' },
|
|
54
|
+
];
|
|
55
|
+
if (emptyCount > 0) {
|
|
56
|
+
options.push({
|
|
57
|
+
label: `Clean ${emptyCount} empty sessions`,
|
|
58
|
+
value: 'clean',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
options.push({ label: '', value: 'exit', disabled: true, separator: true });
|
|
62
|
+
options.push({
|
|
63
|
+
label: 'Exit',
|
|
64
|
+
value: 'exit',
|
|
65
|
+
icon: '✕',
|
|
66
|
+
iconColor: '38;5;204',
|
|
67
|
+
});
|
|
68
|
+
const result = await customSelect({
|
|
69
|
+
message: 'Select an option',
|
|
70
|
+
options,
|
|
71
|
+
numbered: true,
|
|
72
|
+
});
|
|
73
|
+
if (isCancel(result) || result === undefined)
|
|
74
|
+
return null;
|
|
75
|
+
if (result === 'exit')
|
|
76
|
+
return null;
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
export const select = async (sessions, project, base) => {
|
|
80
|
+
let page = 0;
|
|
81
|
+
const total = Math.ceil(sessions.length / PAGE_SIZE);
|
|
82
|
+
pages: while (true) {
|
|
83
|
+
printHeader({ Project: project, Base: base });
|
|
84
|
+
const start = page * PAGE_SIZE;
|
|
85
|
+
const slice = sessions.slice(start, start + PAGE_SIZE);
|
|
86
|
+
const columns = getColumns();
|
|
87
|
+
const labelMax = columns - CLACK_PREFIX - HEADER_MARGIN;
|
|
88
|
+
const options = slice.map((session, index) => ({
|
|
89
|
+
label: truncate(session.message, labelMax),
|
|
90
|
+
value: start + index,
|
|
91
|
+
}));
|
|
92
|
+
options.push({ label: '', value: EXIT, disabled: true, separator: true });
|
|
93
|
+
let actionNumber = 1;
|
|
94
|
+
if (page > 0) {
|
|
95
|
+
options.push({
|
|
96
|
+
label: 'Previous',
|
|
97
|
+
value: PREVIOUS,
|
|
98
|
+
icon: '←',
|
|
99
|
+
number: actionNumber,
|
|
100
|
+
});
|
|
101
|
+
actionNumber++;
|
|
102
|
+
}
|
|
103
|
+
if (page < total - 1) {
|
|
104
|
+
options.push({
|
|
105
|
+
label: 'Next',
|
|
106
|
+
value: NEXT,
|
|
107
|
+
icon: '→',
|
|
108
|
+
number: actionNumber,
|
|
109
|
+
});
|
|
110
|
+
actionNumber++;
|
|
111
|
+
}
|
|
112
|
+
options.push({ label: '', value: EXIT, disabled: true, separator: true });
|
|
113
|
+
options.push({
|
|
114
|
+
label: 'Back to menu',
|
|
115
|
+
value: EXIT,
|
|
116
|
+
icon: '☰',
|
|
117
|
+
number: actionNumber,
|
|
118
|
+
});
|
|
119
|
+
options.push({
|
|
120
|
+
label: 'Exit',
|
|
121
|
+
value: CANCEL,
|
|
122
|
+
icon: '✕',
|
|
123
|
+
iconColor: '38;5;204',
|
|
124
|
+
number: EXIT_NUMBER,
|
|
125
|
+
});
|
|
126
|
+
const result = await customSelect({
|
|
127
|
+
message: `Select a session (${page + 1}/${total})`,
|
|
128
|
+
options,
|
|
129
|
+
shortcuts: buildShortcuts(options),
|
|
130
|
+
});
|
|
131
|
+
if (isCancel(result) || result === undefined)
|
|
132
|
+
return null;
|
|
133
|
+
if (result === EXIT)
|
|
134
|
+
return null;
|
|
135
|
+
if (result === CANCEL)
|
|
136
|
+
return 'exit';
|
|
137
|
+
if (result === NEXT) {
|
|
138
|
+
page++;
|
|
139
|
+
clear();
|
|
140
|
+
continue pages;
|
|
141
|
+
}
|
|
142
|
+
if (result === PREVIOUS) {
|
|
143
|
+
page--;
|
|
144
|
+
clear();
|
|
145
|
+
continue pages;
|
|
146
|
+
}
|
|
147
|
+
return sessions[result] ?? null;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
export const selectAction = async (session, project, base, previewLines, subagentCount) => {
|
|
151
|
+
const fields = {
|
|
152
|
+
Project: project,
|
|
153
|
+
Base: base,
|
|
154
|
+
ID: session.id,
|
|
155
|
+
Date: session.date,
|
|
156
|
+
Title: session.message,
|
|
157
|
+
};
|
|
158
|
+
if (subagentCount > 0)
|
|
159
|
+
fields.Subagents = String(subagentCount);
|
|
160
|
+
printHeader(fields);
|
|
161
|
+
for (const line of previewLines)
|
|
162
|
+
console.log(line);
|
|
163
|
+
if (previewLines.length > 0)
|
|
164
|
+
console.log();
|
|
165
|
+
const options = [
|
|
166
|
+
{ label: 'Open', value: 'open' },
|
|
167
|
+
{ label: 'Stats', value: 'stats' },
|
|
168
|
+
{ label: 'Fork', value: 'fork' },
|
|
169
|
+
{ label: 'Merge', value: 'merge' },
|
|
170
|
+
{ label: 'Prune', value: 'prune' },
|
|
171
|
+
{ label: 'Trim', value: 'trim' },
|
|
172
|
+
{ label: 'Rename', value: 'rename' },
|
|
173
|
+
{ label: 'Delete', value: 'delete' },
|
|
174
|
+
{ label: '', value: 'back', disabled: true, separator: true },
|
|
175
|
+
{ label: 'Back', value: 'back', icon: '←' },
|
|
176
|
+
{ label: 'Exit', value: 'exit', icon: '✕', iconColor: '38;5;204' },
|
|
177
|
+
];
|
|
178
|
+
const result = await customSelect({
|
|
179
|
+
message: 'Choose an action',
|
|
180
|
+
options,
|
|
181
|
+
numbered: true,
|
|
182
|
+
});
|
|
183
|
+
if (isCancel(result) || result === undefined)
|
|
184
|
+
return null;
|
|
185
|
+
return result;
|
|
186
|
+
};
|
|
187
|
+
export const selectMultiple = async (sessions, project, base) => {
|
|
188
|
+
const selected = new Set();
|
|
189
|
+
let page = 0;
|
|
190
|
+
const total = Math.ceil(sessions.length / PAGE_SIZE);
|
|
191
|
+
picks: while (true) {
|
|
192
|
+
printHeader({ Project: project, Base: base });
|
|
193
|
+
const start = page * PAGE_SIZE;
|
|
194
|
+
const slice = sessions.slice(start, start + PAGE_SIZE);
|
|
195
|
+
const columns = getColumns();
|
|
196
|
+
const labelMax = columns - CLACK_PREFIX - HEADER_MARGIN - CHECKBOX_WIDTH;
|
|
197
|
+
const options = slice.map((session, index) => {
|
|
198
|
+
const absolute = start + index;
|
|
199
|
+
const checkbox = selected.has(absolute) ? '☑' : '☐';
|
|
200
|
+
return {
|
|
201
|
+
label: `${checkbox} ${truncate(session.message, labelMax)}`,
|
|
202
|
+
value: absolute,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
options.push({ label: '', value: CANCEL, disabled: true, separator: true });
|
|
206
|
+
let actionNumber = 1;
|
|
207
|
+
if (page > 0) {
|
|
208
|
+
options.push({
|
|
209
|
+
label: 'Previous',
|
|
210
|
+
value: PREVIOUS,
|
|
211
|
+
icon: '←',
|
|
212
|
+
number: actionNumber,
|
|
213
|
+
});
|
|
214
|
+
actionNumber++;
|
|
215
|
+
}
|
|
216
|
+
if (page < total - 1) {
|
|
217
|
+
options.push({
|
|
218
|
+
label: 'Next',
|
|
219
|
+
value: NEXT,
|
|
220
|
+
icon: '→',
|
|
221
|
+
number: actionNumber,
|
|
222
|
+
});
|
|
223
|
+
actionNumber++;
|
|
224
|
+
}
|
|
225
|
+
options.push({
|
|
226
|
+
label: `Confirm (${selected.size})`,
|
|
227
|
+
value: CONFIRM,
|
|
228
|
+
icon: '✓',
|
|
229
|
+
number: actionNumber,
|
|
230
|
+
});
|
|
231
|
+
options.push({
|
|
232
|
+
label: 'Cancel',
|
|
233
|
+
value: CANCEL,
|
|
234
|
+
icon: '✕',
|
|
235
|
+
iconColor: '38;5;204',
|
|
236
|
+
number: CANCEL_NUMBER,
|
|
237
|
+
});
|
|
238
|
+
const result = await customSelect({
|
|
239
|
+
message: `Select sessions to merge (${page + 1}/${total})`,
|
|
240
|
+
options,
|
|
241
|
+
shortcuts: buildShortcuts(options),
|
|
242
|
+
});
|
|
243
|
+
if (isCancel(result) || result === undefined)
|
|
244
|
+
return null;
|
|
245
|
+
if (result === CANCEL)
|
|
246
|
+
return null;
|
|
247
|
+
if (result === NEXT) {
|
|
248
|
+
page++;
|
|
249
|
+
clear();
|
|
250
|
+
continue picks;
|
|
251
|
+
}
|
|
252
|
+
if (result === PREVIOUS) {
|
|
253
|
+
page--;
|
|
254
|
+
clear();
|
|
255
|
+
continue picks;
|
|
256
|
+
}
|
|
257
|
+
if (result === CONFIRM) {
|
|
258
|
+
if (selected.size === 0)
|
|
259
|
+
continue picks;
|
|
260
|
+
return [...selected]
|
|
261
|
+
.map((index) => sessions[index])
|
|
262
|
+
.filter((session) => session !== undefined);
|
|
263
|
+
}
|
|
264
|
+
if (selected.has(result)) {
|
|
265
|
+
selected.delete(result);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
selected.add(result);
|
|
269
|
+
}
|
|
270
|
+
clear();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
export const promptTitle = async (fields) => {
|
|
274
|
+
showHeader(fields);
|
|
275
|
+
const result = await text({
|
|
276
|
+
message: 'New title (Esc to cancel)',
|
|
277
|
+
placeholder: 'Type the new session title...',
|
|
278
|
+
});
|
|
279
|
+
if (isCancel(result))
|
|
280
|
+
return null;
|
|
281
|
+
return result;
|
|
282
|
+
};
|
|
283
|
+
export const promptConfirm = async (message) => {
|
|
284
|
+
const result = await customSelect({
|
|
285
|
+
message,
|
|
286
|
+
options: [
|
|
287
|
+
{ label: 'Yes', value: true },
|
|
288
|
+
{ label: 'No', value: false },
|
|
289
|
+
],
|
|
290
|
+
numbered: true,
|
|
291
|
+
});
|
|
292
|
+
if (isCancel(result) || result === undefined)
|
|
293
|
+
return null;
|
|
294
|
+
return result;
|
|
295
|
+
};
|
|
296
|
+
export const selectPruneOptions = async (stats, fields) => {
|
|
297
|
+
const entries = [];
|
|
298
|
+
if (stats.toolBlocks > 0)
|
|
299
|
+
entries.push({
|
|
300
|
+
key: 'toolBlocks',
|
|
301
|
+
label: `${stats.toolBlocks} tool blocks`,
|
|
302
|
+
});
|
|
303
|
+
if (stats.emptyMessages > 0)
|
|
304
|
+
entries.push({
|
|
305
|
+
key: 'emptyMessages',
|
|
306
|
+
label: `${stats.emptyMessages} empty messages`,
|
|
307
|
+
});
|
|
308
|
+
if (stats.shortMessages > 0)
|
|
309
|
+
entries.push({
|
|
310
|
+
key: 'shortMessages',
|
|
311
|
+
label: `${stats.shortMessages} short messages (<50 chars)`,
|
|
312
|
+
});
|
|
313
|
+
if (stats.systemTags > 0)
|
|
314
|
+
entries.push({
|
|
315
|
+
key: 'systemTags',
|
|
316
|
+
label: `${stats.systemTags} system/IDE tags`,
|
|
317
|
+
});
|
|
318
|
+
if (entries.length === 0) {
|
|
319
|
+
showHeader(fields);
|
|
320
|
+
const result = await customSelect({
|
|
321
|
+
message: 'Nothing to prune',
|
|
322
|
+
options: [
|
|
323
|
+
{ label: '', value: 'back', disabled: true, separator: true },
|
|
324
|
+
{ label: 'Back', value: 'back', icon: '←' },
|
|
325
|
+
{ label: 'Exit', value: 'exit', icon: '✕', iconColor: '38;5;204' },
|
|
326
|
+
],
|
|
327
|
+
numbered: true,
|
|
328
|
+
});
|
|
329
|
+
if (result === 'exit')
|
|
330
|
+
return 'exit';
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const selected = new Set();
|
|
334
|
+
let cursor;
|
|
335
|
+
for (let index = 0; index < entries.length; index++)
|
|
336
|
+
selected.add(index);
|
|
337
|
+
toggle: while (true) {
|
|
338
|
+
showHeader(fields);
|
|
339
|
+
const options = entries.map((entry, index) => {
|
|
340
|
+
const checkbox = selected.has(index) ? '☑' : '☐';
|
|
341
|
+
return { label: `${checkbox} ${entry.label}`, value: index };
|
|
342
|
+
});
|
|
343
|
+
options.push({ label: '', value: CANCEL, disabled: true, separator: true });
|
|
344
|
+
options.push({
|
|
345
|
+
label: `Confirm (${selected.size})`,
|
|
346
|
+
value: CONFIRM,
|
|
347
|
+
icon: '✓',
|
|
348
|
+
number: 1,
|
|
349
|
+
});
|
|
350
|
+
options.push({
|
|
351
|
+
label: 'Cancel',
|
|
352
|
+
value: CANCEL,
|
|
353
|
+
icon: '✕',
|
|
354
|
+
iconColor: '38;5;204',
|
|
355
|
+
number: CANCEL_NUMBER,
|
|
356
|
+
});
|
|
357
|
+
const result = await customSelect({
|
|
358
|
+
message: 'Select what to prune',
|
|
359
|
+
options,
|
|
360
|
+
initialValue: cursor,
|
|
361
|
+
shortcuts: buildShortcuts(options),
|
|
362
|
+
});
|
|
363
|
+
if (isCancel(result) || result === undefined)
|
|
364
|
+
return null;
|
|
365
|
+
if (result === CANCEL)
|
|
366
|
+
return null;
|
|
367
|
+
if (result === CONFIRM) {
|
|
368
|
+
if (selected.size === 0)
|
|
369
|
+
continue toggle;
|
|
370
|
+
return new Set([...selected]
|
|
371
|
+
.map((index) => entries[index]?.key)
|
|
372
|
+
.filter((key) => key !== undefined));
|
|
373
|
+
}
|
|
374
|
+
if (selected.has(result)) {
|
|
375
|
+
selected.delete(result);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
selected.add(result);
|
|
379
|
+
}
|
|
380
|
+
cursor = result;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
export const selectTrimLine = async (messages, fields) => {
|
|
384
|
+
let page = 0;
|
|
385
|
+
const total = Math.ceil(messages.length / PAGE_SIZE);
|
|
386
|
+
pages: while (true) {
|
|
387
|
+
showHeader(fields);
|
|
388
|
+
const start = page * PAGE_SIZE;
|
|
389
|
+
const slice = messages.slice(start, start + PAGE_SIZE);
|
|
390
|
+
const columns = getColumns();
|
|
391
|
+
const labelMax = columns - CLACK_PREFIX - HEADER_MARGIN;
|
|
392
|
+
const options = slice.map(([ordinal, label]) => ({
|
|
393
|
+
label: truncate(label, labelMax),
|
|
394
|
+
value: ordinal,
|
|
395
|
+
}));
|
|
396
|
+
options.push({ label: '', value: CANCEL, disabled: true, separator: true });
|
|
397
|
+
let actionNumber = 1;
|
|
398
|
+
if (page > 0) {
|
|
399
|
+
options.push({
|
|
400
|
+
label: 'Previous',
|
|
401
|
+
value: PREVIOUS,
|
|
402
|
+
icon: '←',
|
|
403
|
+
number: actionNumber,
|
|
404
|
+
});
|
|
405
|
+
actionNumber++;
|
|
406
|
+
}
|
|
407
|
+
if (page < total - 1) {
|
|
408
|
+
options.push({
|
|
409
|
+
label: 'Next',
|
|
410
|
+
value: NEXT,
|
|
411
|
+
icon: '→',
|
|
412
|
+
number: actionNumber,
|
|
413
|
+
});
|
|
414
|
+
actionNumber++;
|
|
415
|
+
}
|
|
416
|
+
options.push({
|
|
417
|
+
label: 'Cancel',
|
|
418
|
+
value: CANCEL,
|
|
419
|
+
icon: '✕',
|
|
420
|
+
iconColor: '38;5;204',
|
|
421
|
+
number: CANCEL_NUMBER,
|
|
422
|
+
});
|
|
423
|
+
const result = await customSelect({
|
|
424
|
+
message: `Select last message to keep (${page + 1}/${total})`,
|
|
425
|
+
options,
|
|
426
|
+
shortcuts: buildShortcuts(options),
|
|
427
|
+
});
|
|
428
|
+
if (isCancel(result) || result === undefined)
|
|
429
|
+
return null;
|
|
430
|
+
if (result === CANCEL)
|
|
431
|
+
return null;
|
|
432
|
+
if (result === NEXT) {
|
|
433
|
+
page++;
|
|
434
|
+
clear();
|
|
435
|
+
continue pages;
|
|
436
|
+
}
|
|
437
|
+
if (result === PREVIOUS) {
|
|
438
|
+
page--;
|
|
439
|
+
clear();
|
|
440
|
+
continue pages;
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
export const showProjectStats = async (lines, fields) => {
|
|
446
|
+
printHeader(fields);
|
|
447
|
+
for (const line of lines)
|
|
448
|
+
console.log(line);
|
|
449
|
+
if (lines.length > 0)
|
|
450
|
+
console.log();
|
|
451
|
+
await customSelect({
|
|
452
|
+
message: 'Stats',
|
|
453
|
+
options: [
|
|
454
|
+
{ label: '', value: 'back', disabled: true, separator: true },
|
|
455
|
+
{ label: 'Back', value: 'back', icon: '←' },
|
|
456
|
+
],
|
|
457
|
+
numbered: true,
|
|
458
|
+
});
|
|
459
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sessioner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "✨ An interactive CLI to navigate and manage Claude Code sessions from the terminal.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sessioner": "lib/bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/wellwelwel/sessioner.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/wellwelwel/sessioner/issues"
|
|
16
|
+
},
|
|
17
|
+
"author": "https://github.com/wellwelwel",
|
|
18
|
+
"funding": {
|
|
19
|
+
"type": "github",
|
|
20
|
+
"url": "https://github.com/sponsors/wellwelwel"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"lib"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "rm -rf lib && tsc",
|
|
27
|
+
"start": "tsx src/bin/index.ts",
|
|
28
|
+
"lint": "prettier --check .",
|
|
29
|
+
"lint:fix": "prettier --write .",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@clack/core": "^1.0.1",
|
|
34
|
+
"@clack/prompts": "^1.0.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
|
|
38
|
+
"@types/node": "^25.3.1",
|
|
39
|
+
"prettier": "^3.8.1",
|
|
40
|
+
"tsx": "^4.21.0",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"claude",
|
|
48
|
+
"session",
|
|
49
|
+
"sessions",
|
|
50
|
+
"claude-code",
|
|
51
|
+
"cli",
|
|
52
|
+
"terminal",
|
|
53
|
+
"interactive",
|
|
54
|
+
"management",
|
|
55
|
+
"navigation",
|
|
56
|
+
"session-viewer",
|
|
57
|
+
"claude-stats",
|
|
58
|
+
"claude-session-viewer",
|
|
59
|
+
"claude-session-stats",
|
|
60
|
+
"claude-opus",
|
|
61
|
+
"claude-sonnet",
|
|
62
|
+
"claude-haiku"
|
|
63
|
+
]
|
|
64
|
+
}
|