aisessions 1.1.2 → 1.1.4

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/bin/cli.js CHANGED
@@ -2,8 +2,13 @@
2
2
  import { createServer } from '../src/server.js'
3
3
  import { parseArgs } from 'node:util'
4
4
  import { exec } from 'node:child_process'
5
+ import pc from 'picocolors'
5
6
 
6
- const VERSION = '1.1.2'
7
+ const VERSION = '1.1.4'
8
+ const DEV_HANDLE = 's41r4j'
9
+ const GITHUB_URL = 'https://github.com/s41r4j/aisessions'
10
+ const ISSUE_URL = 'https://github.com/s41r4j/aisessions/issues'
11
+ const X_URL = 'https://x.com/s41r4j'
7
12
 
8
13
  const { values } = parseArgs({
9
14
  options: {
@@ -44,9 +49,49 @@ if (values.help) {
44
49
  const PORT = parseInt(values.port, 10) || 7878
45
50
  const server = createServer()
46
51
 
52
+ function printBanner(url) {
53
+ const art = [
54
+ ' _ _ ',
55
+ ' __ _ (_) ___ ___ ___ ___ ___(_) ___ _ __ ___ ',
56
+ ' / _` || |/ __|/ _ \\/ __|/ _ \\/ __| |/ _ \\| `_ \\/ __|',
57
+ '| (_| || |\\__ \\ __/\\__ \\ __/\\__ \\ | (_) | | | \\__ \\',
58
+ ' \\__,_||_||___/\\___||___/\\___||___/_|\\___/|_| |_|___/',
59
+ ]
60
+
61
+ console.log('')
62
+ art.forEach(line => console.log(pc.cyan(line)))
63
+ console.log('')
64
+ console.log(`${pc.bold(' aisessions')} ${pc.dim('v' + VERSION)} ${pc.green('ready')}`)
65
+ console.log(` ${pc.dim('Local UI:')} ${pc.underline(url)}`)
66
+ console.log(` ${pc.dim('Data store:')} ~/.aisessions/trash ~/.aisessions/backups`)
67
+ console.log(` ${pc.dim('Developer:')} @${DEV_HANDLE}`)
68
+ console.log(` ${pc.dim('GitHub:')} ${GITHUB_URL}`)
69
+ console.log(` ${pc.dim('X:')} ${X_URL}`)
70
+ console.log(` ${pc.dim('Report issue:')} ${ISSUE_URL}`)
71
+ console.log('')
72
+ }
73
+
74
+ function startStatusLine(url) {
75
+ if (!process.stdout.isTTY) return
76
+ const frames = ['-', '\\\\', '|', '/']
77
+ let i = 0
78
+ const timer = setInterval(() => {
79
+ const frame = frames[i++ % frames.length]
80
+ process.stdout.write(`\\r ${pc.cyan(frame)} ${pc.dim('running at')} ${url} ${pc.dim('Ctrl+C to stop')} `)
81
+ }, 1200)
82
+ process.once('SIGINT', () => {
83
+ clearInterval(timer)
84
+ process.stdout.clearLine?.(0)
85
+ process.stdout.cursorTo?.(0)
86
+ console.log(pc.dim(' stopped aisessions'))
87
+ process.exit(0)
88
+ })
89
+ }
90
+
47
91
  server.listen(PORT, '127.0.0.1', () => {
48
92
  const url = `http://127.0.0.1:${PORT}`
49
- console.log(`\n aisessions ready -> ${url}\n`)
93
+ printBanner(url)
94
+ startStatusLine(url)
50
95
 
51
96
  if (!values['no-open']) {
52
97
  const cmd = process.platform === 'darwin' ? `open "${url}"` :
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aisessions",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Browse, manage and analyze your AI coding agent sessions — Claude Code, Codex, and more",
5
5
  "bin": {
6
6
  "aisessions": "bin/cli.js"
@@ -27,6 +27,7 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
- "ccusage": "^20.0.6"
30
+ "ccusage": "^20.0.6",
31
+ "picocolors": "^1.1.1"
31
32
  }
32
33
  }
package/src/ui/css.js CHANGED
@@ -373,7 +373,7 @@ html[data-theme="light"] .credit-pkg{color:rgba(24,24,20,.32)}
373
373
  .ctx-banner .ctx-label{color:var(--accent);letter-spacing:1px}
374
374
 
375
375
  /* ── USAGE PANEL ─────────────────────────────────────────────────────────────── */
376
- #usage-panel{overflow-y:auto;display:block;padding:0}
376
+ #usage-panel{overflow-y:auto;padding:0}
377
377
 
378
378
  .stat-grid{
379
379
  display:grid;
package/src/ui/js.js CHANGED
@@ -76,6 +76,11 @@ document.addEventListener('click', function (e) {
76
76
  if (a === 'delete-backup') { deleteBackup(meta); return; }
77
77
  if (a === 'view-session') { viewSession(p); return; }
78
78
  if (a === 'close-modal') { closeModal(); return; }
79
+ if (a === 'select-all') { selectAllSessions(); return; }
80
+ if (a === 'select-none') { selectNoSessions(); return; }
81
+ if (a === 'toggle-group') { toggleGrouping(); return; }
82
+ if (a === 'trash-selected') { trashSelected(); return; }
83
+ if (a === 'backup-selected'){ backupSelected(); return; }
79
84
  return;
80
85
  }
81
86
  var row = e.target.closest('.tbl-row');
@@ -305,35 +310,60 @@ function rowHtml(s) {
305
310
  }
306
311
 
307
312
  // ── TOOLBAR ───────────────────────────────────────────────────────────────────
308
- function wireBtn(id, fn) { var el = $(id); if (el) el.addEventListener('click', fn); }
313
+ function selectAllSessions() {
314
+ filtered.forEach(function(s) { selected.add(s.path); });
315
+ renderSessions();
316
+ }
317
+
318
+ function selectNoSessions() {
319
+ selected.clear();
320
+ renderSessions();
321
+ }
309
322
 
310
- wireBtn('btn-sel-all', function() { filtered.forEach(function(s) { selected.add(s.path); }); renderSessions(); });
311
- wireBtn('btn-sel-none', function() { selected.clear(); renderSessions(); });
312
- wireBtn('btn-group', function() {
323
+ function toggleGrouping() {
313
324
  grouped = !grouped;
314
325
  var el = $('btn-group');
315
326
  if (el) { el.textContent = grouped ? 'FLAT VIEW' : 'GROUP VIEW'; el.classList.toggle('active-btn', !grouped); }
316
- PAGE = 0; renderSessions();
317
- });
327
+ PAGE = 0;
328
+ renderSessions();
329
+ }
318
330
 
319
- wireBtn('btn-trash', async function() {
331
+ async function trashSelected() {
320
332
  if (!selected.size) { toast('SELECT SESSIONS FIRST', 'err'); return; }
321
333
  if (!confirm('Move ' + selected.size + ' session(s) to trash?')) return;
322
334
  var items = buildItems(Array.from(selected));
323
- var res = await post('/api/trash/move', { items: items });
335
+ var res;
336
+ try { res = await post('/api/trash/move', { items: items }); }
337
+ catch (e) { toast('TRASH FAILED: ' + e.message, 'err', 5000); return; }
338
+ if (!Array.isArray(res)) { toast('TRASH FAILED: INVALID SERVER RESPONSE', 'err', 5000); return; }
324
339
  var ok = res.filter(function(r) { return r.ok; }).map(function(r) { return r.path; });
340
+ var fail = res.filter(function(r) { return !r.ok; });
325
341
  ok.forEach(function(p) { ALL = ALL.filter(function(s) { return s.path !== p; }); selected.delete(p); });
326
- applyFilter(); toast(ok.length + ' MOVED TO TRASH', 'ok');
327
- if (ok.length) switchTab('trash');
328
- });
329
- wireBtn('btn-backup', async function() {
342
+ applyFilter();
343
+ if (ok.length) {
344
+ toast(ok.length + ' MOVED TO TRASH' + (fail.length ? ' / ' + fail.length + ' FAILED' : ''), fail.length ? 'err' : 'ok');
345
+ switchTab('trash');
346
+ } else {
347
+ toast('TRASH FAILED: ' + ((fail[0] && fail[0].error) || 'NO ITEMS MOVED'), 'err', 5000);
348
+ }
349
+ }
350
+
351
+ async function backupSelected() {
330
352
  if (!selected.size) { toast('SELECT SESSIONS FIRST', 'err'); return; }
331
353
  var items = buildItems(Array.from(selected));
332
- var res = await post('/api/backup/create', { items: items, note: '' });
333
- var ok = res.filter(function(r) { return r.ok; }).length;
334
- toast(ok + ' BACKUP(S) CREATED', 'ok');
335
- if (ok) switchTab('backups');
336
- });
354
+ var res;
355
+ try { res = await post('/api/backup/create', { items: items, note: '' }); }
356
+ catch (e) { toast('BACKUP FAILED: ' + e.message, 'err', 5000); return; }
357
+ if (!Array.isArray(res)) { toast('BACKUP FAILED: INVALID SERVER RESPONSE', 'err', 5000); return; }
358
+ var ok = res.filter(function(r) { return r.ok; });
359
+ var fail = res.filter(function(r) { return !r.ok; });
360
+ if (ok.length) {
361
+ toast(ok.length + ' BACKUP(S) CREATED' + (fail.length ? ' / ' + fail.length + ' FAILED' : ''), fail.length ? 'err' : 'ok');
362
+ switchTab('backups');
363
+ } else {
364
+ toast('BACKUP FAILED: ' + ((fail[0] && fail[0].error) || 'NO BACKUPS CREATED'), 'err', 5000);
365
+ }
366
+ }
337
367
 
338
368
  // Enrich paths with session metadata for storage
339
369
  function buildItems(paths) {
@@ -369,7 +399,10 @@ document.querySelectorAll('.tab').forEach(function(t) {
369
399
  async function trashOne(path) {
370
400
  if (!path || !confirm('Move to trash?')) return;
371
401
  var items = buildItems([path]);
372
- var res = await post('/api/trash/move', { items: items });
402
+ var res;
403
+ try { res = await post('/api/trash/move', { items: items }); }
404
+ catch (e) { toast('ERROR: ' + e.message, 'err', 5000); return; }
405
+ if (!Array.isArray(res)) { toast('ERROR: INVALID SERVER RESPONSE', 'err', 5000); return; }
373
406
  if (res[0] && res[0].ok) {
374
407
  ALL = ALL.filter(function(s) { return s.path !== path; }); selected.delete(path);
375
408
  applyFilter(); toast('MOVED TO TRASH', 'ok'); switchTab('trash');
@@ -378,7 +411,10 @@ async function trashOne(path) {
378
411
  async function backupOne(path) {
379
412
  if (!path) return;
380
413
  var items = buildItems([path]);
381
- var res = await post('/api/backup/create', { items: items, note: '' });
414
+ var res;
415
+ try { res = await post('/api/backup/create', { items: items, note: '' }); }
416
+ catch (e) { toast('ERROR: ' + e.message, 'err', 5000); return; }
417
+ if (!Array.isArray(res)) { toast('ERROR: INVALID SERVER RESPONSE', 'err', 5000); return; }
382
418
  if (res[0] && res[0].ok) { toast('BACKUP CREATED', 'ok'); switchTab('backups'); }
383
419
  else toast('ERROR: ' + ((res[0] && res[0].error) || '?'), 'err');
384
420
  }
package/src/ui/render.js CHANGED
@@ -65,7 +65,7 @@ export function renderPage() {
65
65
  <span>X.com</span>
66
66
  </a>
67
67
  </div>
68
- <div class="credit-pkg">aisessions v1.1.2</div>
68
+ <div class="credit-pkg">aisessions v1.1.4</div>
69
69
  </div>
70
70
  </nav>
71
71
 
@@ -86,14 +86,14 @@ export function renderPage() {
86
86
  <!-- ── Sessions ── -->
87
87
  <div id="sessions-panel" class="panel active">
88
88
  <div id="toolbar">
89
- <button class="btn" id="btn-sel-all">SELECT ALL</button>
90
- <button class="btn" id="btn-sel-none">DESELECT</button>
91
- <button class="btn" id="btn-group">FLAT VIEW</button>
89
+ <button class="btn" id="btn-sel-all" data-action="select-all">SELECT ALL</button>
90
+ <button class="btn" id="btn-sel-none" data-action="select-none">DESELECT</button>
91
+ <button class="btn" id="btn-group" data-action="toggle-group">FLAT VIEW</button>
92
92
  <span class="spacer"></span>
93
93
  <span id="sel-info">...</span>
94
94
  <span class="spacer"></span>
95
- <button class="btn success" id="btn-backup">BACKUP SEL</button>
96
- <button class="btn danger" id="btn-trash">TRASH SEL</button>
95
+ <button class="btn success" id="btn-backup" data-action="backup-selected">BACKUP SEL</button>
96
+ <button class="btn danger" id="btn-trash" data-action="trash-selected">TRASH SEL</button>
97
97
  </div>
98
98
  <div id="tbl-wrap">
99
99
  <div class="tbl-hdr">
@@ -125,7 +125,7 @@ export function renderPage() {
125
125
 
126
126
  <!-- FOOTER -->
127
127
  <footer id="foot">
128
- <span>AISESSIONS v1.1.2</span>
128
+ <span>AISESSIONS v1.1.4</span>
129
129
  <span>&nbsp;|&nbsp;</span>
130
130
  <span id="foot-info">LOADING...</span>
131
131
  </footer>