session-preserver 0.1.1 → 0.1.2

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 +57 -3
  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,60 @@ 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↑/↓ move · enter resume in its CLI · s copy summary · q quit\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
+ if (key === "q" || key === "\x03" || key === "\x1b") return done("quit");
302
+ if (key === "\x1b[A" || key === "k") { sel = Math.max(0, sel - 1); return draw(); } // up
303
+ if (key === "\x1b[B" || key === "j") { sel = Math.min(rows.length - 1, sel + 1); return draw(); } // down
304
+ if (key === "\r" || key === "\n") return done("open");
305
+ if (key === "s") return done("summary");
306
+ });
307
+ process.stdout.write("\x1b[?1049h"); // alt screen (after raw mode so first draw is clean)
308
+ draw();
309
+ return done;
310
+ }
311
+
261
312
  function cmdShow(id, n) {
262
313
  const sess = find(id);
263
314
  let pairs = [...conversation(sess)];
@@ -277,7 +328,8 @@ function usage(code) {
277
328
  console.log(`session-preserver - centralized AI CLI session history
278
329
 
279
330
  Usage:
280
- session-preserver [ls] [-n N] [--since 2d] [--tool claude|codex|opencode] [query]
331
+ session-preserver [ls] [-n N] [--since 2d] [--tool claude|codex|opencode] [--plain] [query]
332
+ (interactive picker in a TTY: ↑/↓ move, enter resume, s copy summary, q quit; --plain forces table)
281
333
  session-preserver show <id> [-n N]
282
334
  session-preserver summary <id>
283
335
  session-preserver path <id>
@@ -302,7 +354,9 @@ if (cmd === "ls") {
302
354
  else if (argv[i] === "--tool") {
303
355
  args.tool = argv[++i];
304
356
  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];
357
+ }
358
+ else if (argv[i] === "--plain") args.plain = true;
359
+ else if (!args.query) args.query = argv[i];
306
360
  else usage(1);
307
361
  }
308
362
  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.2",
4
4
  "description": "Centralized session history for AI CLIs — Claude Code, Codex, OpenCode. Read-only.",
5
5
  "license": "MIT",
6
6
  "bin": {