session-preserver 0.1.1 → 0.1.3

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.
Files changed (2) hide show
  1. package/index.js +66 -5
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -239,6 +239,9 @@ function cmdOpen(id) {
239
239
  }
240
240
  const cwd = bin.cwd && fs.existsSync(bin.cwd) ? bin.cwd : process.cwd();
241
241
  console.log(`resuming in ${sess.tool}: ${bin.cmd} ${bin.args.join(" ")} (cwd: ${shortHome(cwd)})`);
242
+ // reset terminal for the child TUI (we may be exiting raw/alt-screen state)
243
+ if (process.stdout.isTTY) process.stdout.write("\x1b[?1049l\x1b[0m\x1b[?25h");
244
+ if (process.stdin.isTTY) { try { process.stdin.setRawMode(false); } catch {} }
242
245
  const r = spawnSync(bin.cmd, bin.args, { cwd, stdio: "inherit" });
243
246
  process.exit(r.status ?? 0);
244
247
  }
@@ -252,12 +255,67 @@ function cmdLs(args) {
252
255
  if (args.query) { const q = args.query.toLowerCase(); rows = rows.filter((r) => (r.title + " " + r.project).toLowerCase().includes(q)); }
253
256
  rows = rows.slice(0, args.n);
254
257
  if (!rows.length) { console.log("no sessions found"); return; }
255
- const idW = Math.max(...rows.map((r) => r.id.length)) ;
258
+ if (args.plain || !process.stdout.isTTY || !process.stdin.isTTY) return printTable(rows);
259
+ pickInteractive(rows);
260
+ }
261
+
262
+ function printTable(rows) {
263
+ const idW = Math.max(...rows.map((r) => r.id.length));
256
264
  console.log(`${"ID".padEnd(idW + 1)} ${"TOOL".padEnd(9)} ${"WHEN".padEnd(10)} ${"PROJECT".padEnd(38)} TITLE`);
257
265
  for (const r of rows)
258
266
  console.log(`${r.id.padEnd(idW + 1)} ${r.tool.padEnd(9)} ${reltime(r.updated).padEnd(10)} ${r.project.slice(-37).padEnd(38)} ${r.title.slice(0, 60)}`);
259
267
  }
260
268
 
269
+ const rowLine = (r) =>
270
+ `${r.tool.padEnd(9)} ${reltime(r.updated).padEnd(10)} ${r.project.slice(-30).padEnd(30)} ${r.title.slice(0, 50)}`;
271
+
272
+ function pickInteractive(rows) {
273
+ let sel = 0, offset = 0;
274
+ const height = () => Math.max(5, (process.stdout.rows || 24) - 3);
275
+
276
+ const draw = () => {
277
+ const out = [`\x1b[H\x1b[J↑/↓ j/k move · PgUp/PgDn page · g/G top/bottom · enter resume · s summary · q quit [${sel + 1}/${rows.length}]\n`];
278
+ const h = height();
279
+ if (sel < offset) offset = sel;
280
+ if (sel >= offset + h) offset = sel - h + 1;
281
+ for (let i = offset; i < Math.min(rows.length, offset + h); i++)
282
+ out.push((i === sel ? "\x1b[7m" : "") + rowLine(rows[i]) + (i === sel ? "\x1b[0m" : ""));
283
+ process.stdout.write(out.join("\n") + "\n");
284
+ };
285
+
286
+ const done = (action) => {
287
+ process.stdin.setRawMode(false);
288
+ process.stdin.pause();
289
+ process.stdin.removeAllListeners("data");
290
+ process.stdout.write("\x1b[?1049l");
291
+ const sess = rows[sel];
292
+ if (action === "quit") return;
293
+ if (action === "open") return cmdOpen(sess.id);
294
+ if (action === "summary") return cmdSummary(sess.id);
295
+ };
296
+
297
+ process.stdin.setRawMode(true);
298
+ process.stdin.resume();
299
+ process.stdin.setEncoding("utf8");
300
+ process.stdin.on("data", (key) => {
301
+ const h = height();
302
+ if (key === "q" || key === "\x03" || key === "\x1b") return done("quit");
303
+ if (key === "\x1b[A" || key === "k") sel = Math.max(0, sel - 1);
304
+ else if (key === "\x1b[B" || key === "j") sel = Math.min(rows.length - 1, sel + 1);
305
+ else if (key === "\x1b[5~" || key === "\x02") sel = Math.max(0, sel - h); // PgUp / Ctrl-B
306
+ else if (key === "\x1b[6~" || key === "\x06") sel = Math.min(rows.length - 1, sel + h); // PgDn / Ctrl-F
307
+ else if (key === "g") sel = 0;
308
+ else if (key === "G") sel = rows.length - 1;
309
+ else if (key === "\r" || key === "\n") return done("open");
310
+ else if (key === "s") return done("summary");
311
+ else return;
312
+ draw();
313
+ });
314
+ process.stdout.write("\x1b[?1049h"); // alt screen (after raw mode so first draw is clean)
315
+ draw();
316
+ return done;
317
+ }
318
+
261
319
  function cmdShow(id, n) {
262
320
  const sess = find(id);
263
321
  let pairs = [...conversation(sess)];
@@ -277,7 +335,8 @@ function usage(code) {
277
335
  console.log(`session-preserver - centralized AI CLI session history
278
336
 
279
337
  Usage:
280
- session-preserver [ls] [-n N] [--since 2d] [--tool claude|codex|opencode] [query]
338
+ session-preserver [ls] [-n N] [--since 2d] [--tool claude|codex|opencode] [--plain] [query]
339
+ (interactive picker in a TTY: ↑/↓ move, enter resume, s copy summary, q quit; --plain forces table)
281
340
  session-preserver show <id> [-n N]
282
341
  session-preserver summary <id>
283
342
  session-preserver path <id>
@@ -295,14 +354,16 @@ let cmd = "ls";
295
354
  if (COMMANDS.has(argv[0])) cmd = argv.shift();
296
355
 
297
356
  if (cmd === "ls") {
298
- const args = { n: 20, since: null, tool: null, query: null };
357
+ const args = { n: 500, since: null, tool: null, query: null };
299
358
  for (let i = 0; i < argv.length; i++) {
300
- if (argv[i] === "-n") args.n = Number(argv[++i]);
359
+ if (argv[i] === "-n") { args.n = Number(argv[++i]); if (!args.n) args.n = Infinity; }
301
360
  else if (argv[i] === "--since") args.since = argv[++i];
302
361
  else if (argv[i] === "--tool") {
303
362
  args.tool = argv[++i];
304
363
  if (!SOURCES[args.tool]) { console.error(`unknown tool '${args.tool}' (claude|codex|opencode)`); process.exit(1); }
305
- } else if (!args.query) args.query = argv[i];
364
+ }
365
+ else if (argv[i] === "--plain") args.plain = true;
366
+ else if (!args.query) args.query = argv[i];
306
367
  else usage(1);
307
368
  }
308
369
  cmdLs(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "session-preserver",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Centralized session history for AI CLIs — Claude Code, Codex, OpenCode. Read-only.",
5
5
  "license": "MIT",
6
6
  "bin": {