aisessions 1.0.0 → 1.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/bin/cli.js +1 -1
- package/package.json +5 -2
- package/src/ui/css.js +57 -1
- package/src/ui/js.js +28 -0
- package/src/ui/render.js +3 -2
- package/src/usage/index.js +20 -17
package/bin/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aisessions",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
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"
|
|
@@ -25,5 +25,8 @@
|
|
|
25
25
|
"aisessions",
|
|
26
26
|
"ccusage"
|
|
27
27
|
],
|
|
28
|
-
"license": "MIT"
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"ccusage": "^20.0.6"
|
|
31
|
+
}
|
|
29
32
|
}
|
package/src/ui/css.js
CHANGED
|
@@ -15,6 +15,7 @@ export const PAGE_CSS = `
|
|
|
15
15
|
--muted:#666;
|
|
16
16
|
--dim:#3a3a3a;
|
|
17
17
|
--accent:#ffffff;
|
|
18
|
+
--logo-shadow:0 0 10px rgba(255,255,255,.25);
|
|
18
19
|
--danger:#ff3333;
|
|
19
20
|
--success:#33cc33;
|
|
20
21
|
--warn:#ccaa00;
|
|
@@ -28,6 +29,24 @@ export const PAGE_CSS = `
|
|
|
28
29
|
--logo: 'Press Start 2P', monospace;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
html[data-theme="light"]{
|
|
33
|
+
--bg:#f4f1e8;
|
|
34
|
+
--sb:#ebe6d8;
|
|
35
|
+
--surf:#f8f5ec;
|
|
36
|
+
--surf2:#ede8dc;
|
|
37
|
+
--surf3:#e1dacb;
|
|
38
|
+
--border:rgba(24,24,20,0.12);
|
|
39
|
+
--border2:rgba(24,24,20,0.24);
|
|
40
|
+
--text:#181814;
|
|
41
|
+
--muted:#6f695d;
|
|
42
|
+
--dim:#a49c8d;
|
|
43
|
+
--accent:#11110f;
|
|
44
|
+
--logo-shadow:none;
|
|
45
|
+
--danger:#b42323;
|
|
46
|
+
--success:#197a33;
|
|
47
|
+
--warn:#8a6900;
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
html,body{height:100%;background:var(--bg);color:var(--text);overflow:hidden;
|
|
32
51
|
font-family:var(--pixel);font-size:20px;line-height:1.2;
|
|
33
52
|
-webkit-font-smoothing:none;font-smooth:never;image-rendering:pixelated}
|
|
@@ -66,7 +85,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);overflow:hidden;
|
|
|
66
85
|
#logo{
|
|
67
86
|
font-family:var(--logo);font-size:11px;
|
|
68
87
|
color:var(--accent);letter-spacing:1px;
|
|
69
|
-
text-shadow:
|
|
88
|
+
text-shadow:var(--logo-shadow);
|
|
70
89
|
white-space:nowrap;
|
|
71
90
|
}
|
|
72
91
|
#hdr-sub{font-size:16px;color:var(--muted);letter-spacing:.5px}
|
|
@@ -79,6 +98,23 @@ html,body{height:100%;background:var(--bg);color:var(--text);overflow:hidden;
|
|
|
79
98
|
}
|
|
80
99
|
#search:focus{border-color:var(--border2);background:var(--surf2)}
|
|
81
100
|
#search::placeholder{color:var(--dim)}
|
|
101
|
+
.theme-toggle{
|
|
102
|
+
width:74px;
|
|
103
|
+
height:39px;
|
|
104
|
+
border:2px solid var(--border);
|
|
105
|
+
background:var(--surf);
|
|
106
|
+
color:var(--muted);
|
|
107
|
+
font-family:var(--pixel);font-size:16px;
|
|
108
|
+
letter-spacing:.8px;
|
|
109
|
+
cursor:pointer;
|
|
110
|
+
-webkit-font-smoothing:none;
|
|
111
|
+
}
|
|
112
|
+
.theme-toggle:hover,.theme-toggle:focus{
|
|
113
|
+
color:var(--accent);
|
|
114
|
+
border-color:var(--border2);
|
|
115
|
+
background:var(--surf2);
|
|
116
|
+
outline:none;
|
|
117
|
+
}
|
|
82
118
|
|
|
83
119
|
/* ── SIDEBAR ─────────────────────────────────────────────────────────────────── */
|
|
84
120
|
.sb-section{padding:10px 0}
|
|
@@ -171,6 +207,26 @@ html,body{height:100%;background:var(--bg);color:var(--text);overflow:hidden;
|
|
|
171
207
|
font-size:12px;color:rgba(255,255,255,.26);letter-spacing:1px;
|
|
172
208
|
text-transform:uppercase;
|
|
173
209
|
}
|
|
210
|
+
html[data-theme="light"] .credit-kicker{color:rgba(24,24,20,.42)}
|
|
211
|
+
html[data-theme="light"] .credit-frame{
|
|
212
|
+
border-color:rgba(24,24,20,.18);
|
|
213
|
+
background:rgba(24,24,20,.025);
|
|
214
|
+
}
|
|
215
|
+
html[data-theme="light"] .credit-frame::before,
|
|
216
|
+
html[data-theme="light"] .credit-frame::after{background:var(--accent)}
|
|
217
|
+
html[data-theme="light"] .credit-link{
|
|
218
|
+
color:rgba(24,24,20,.58);
|
|
219
|
+
background:rgba(24,24,20,.02);
|
|
220
|
+
}
|
|
221
|
+
html[data-theme="light"] .credit-link:hover{
|
|
222
|
+
color:var(--accent);
|
|
223
|
+
background:rgba(24,24,20,.04);
|
|
224
|
+
}
|
|
225
|
+
html[data-theme="light"] .credit-link-icon{
|
|
226
|
+
border-color:rgba(24,24,20,.18);
|
|
227
|
+
color:rgba(24,24,20,.78);
|
|
228
|
+
}
|
|
229
|
+
html[data-theme="light"] .credit-pkg{color:rgba(24,24,20,.32)}
|
|
174
230
|
|
|
175
231
|
/* ── TAB BAR — hidden; sidebar drives navigation ────────────────────────────── */
|
|
176
232
|
#tab-bar{display:none}
|
package/src/ui/js.js
CHANGED
|
@@ -14,6 +14,7 @@ var collapsed = new Set();
|
|
|
14
14
|
var PAGE = 0;
|
|
15
15
|
var PER_PAGE = 100;
|
|
16
16
|
var usageCache = {}; // keyed by agentFilter
|
|
17
|
+
var theme = 'dark';
|
|
17
18
|
|
|
18
19
|
// ── utils ─────────────────────────────────────────────────────────────────────
|
|
19
20
|
function $(id) { return document.getElementById(id); }
|
|
@@ -66,6 +67,7 @@ document.addEventListener('click', function (e) {
|
|
|
66
67
|
if (a === 'switch-tab') { switchTab(nav); return; }
|
|
67
68
|
if (a === 'agent-tab') { switchTab(nav); return; }
|
|
68
69
|
if (a === 'switch-tab-global') { setAgentFilter('all'); switchTab(nav); return; }
|
|
70
|
+
if (a === 'toggle-theme') { toggleTheme(); return; }
|
|
69
71
|
if (a === 'trash-one') { trashOne(p); return; }
|
|
70
72
|
if (a === 'backup-one') { backupOne(p); return; }
|
|
71
73
|
if (a === 'restore-trash') { restoreTrash(meta); return; }
|
|
@@ -95,8 +97,34 @@ document.addEventListener('click', function (e) {
|
|
|
95
97
|
if (gh) { var k = gh.dataset.gkey; collapsed.has(k) ? collapsed.delete(k) : collapsed.add(k); renderSessions(); }
|
|
96
98
|
});
|
|
97
99
|
|
|
100
|
+
// ── THEME ─────────────────────────────────────────────────────────────────────
|
|
101
|
+
function getSavedTheme() {
|
|
102
|
+
try { return localStorage.getItem('aisessions-theme') || 'dark'; }
|
|
103
|
+
catch { return 'dark'; }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function saveTheme(next) {
|
|
107
|
+
try { localStorage.setItem('aisessions-theme', next); } catch {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function applyTheme(next) {
|
|
111
|
+
theme = next === 'light' ? 'light' : 'dark';
|
|
112
|
+
document.documentElement.dataset.theme = theme;
|
|
113
|
+
var btn = $('theme-toggle');
|
|
114
|
+
if (btn) {
|
|
115
|
+
btn.textContent = theme === 'light' ? 'DARK' : 'LIGHT';
|
|
116
|
+
btn.title = 'Switch to ' + (theme === 'light' ? 'dark' : 'light') + ' mode';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function toggleTheme() {
|
|
121
|
+
applyTheme(theme === 'light' ? 'dark' : 'light');
|
|
122
|
+
saveTheme(theme);
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
// ── INIT ──────────────────────────────────────────────────────────────────────
|
|
99
126
|
async function init() {
|
|
127
|
+
applyTheme(getSavedTheme());
|
|
100
128
|
var tblBody = $('tbl-body');
|
|
101
129
|
if (!tblBody) return;
|
|
102
130
|
tblBody.innerHTML = '<div class="loading"><span class="loading-dots">LOADING SESSIONS</span></div>';
|
package/src/ui/render.js
CHANGED
|
@@ -22,6 +22,7 @@ export function renderPage() {
|
|
|
22
22
|
<span id="hdr-sub">// AI SESSION MANAGER</span>
|
|
23
23
|
<div id="hdr-right">
|
|
24
24
|
<input id="search" type="search" placeholder="SEARCH SESSIONS...">
|
|
25
|
+
<button id="theme-toggle" class="theme-toggle" data-action="toggle-theme" type="button" title="Toggle light/dark mode">LIGHT</button>
|
|
25
26
|
</div>
|
|
26
27
|
</header>
|
|
27
28
|
|
|
@@ -64,7 +65,7 @@ export function renderPage() {
|
|
|
64
65
|
<span>X.com</span>
|
|
65
66
|
</a>
|
|
66
67
|
</div>
|
|
67
|
-
<div class="credit-pkg">aisessions v1.
|
|
68
|
+
<div class="credit-pkg">aisessions v1.1</div>
|
|
68
69
|
</div>
|
|
69
70
|
</nav>
|
|
70
71
|
|
|
@@ -124,7 +125,7 @@ export function renderPage() {
|
|
|
124
125
|
|
|
125
126
|
<!-- FOOTER -->
|
|
126
127
|
<footer id="foot">
|
|
127
|
-
<span>AISESSIONS v1.
|
|
128
|
+
<span>AISESSIONS v1.1</span>
|
|
128
129
|
<span> | </span>
|
|
129
130
|
<span id="foot-info">LOADING...</span>
|
|
130
131
|
</footer>
|
package/src/usage/index.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url)
|
|
5
|
+
|
|
6
|
+
function ccusageCliPath() {
|
|
7
|
+
try {
|
|
8
|
+
return require.resolve('ccusage/dist/cli.js')
|
|
9
|
+
} catch {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
}
|
|
2
13
|
|
|
3
14
|
function run(args) {
|
|
4
|
-
const
|
|
15
|
+
const cli = ccusageCliPath()
|
|
16
|
+
if (!cli) return null
|
|
17
|
+
|
|
18
|
+
const r = spawnSync(process.execPath, [cli, ...args, '--json'], {
|
|
5
19
|
timeout: 30_000, maxBuffer: 16 * 1024 * 1024, encoding: 'utf8',
|
|
6
20
|
env: { ...process.env, NO_COLOR: '1' },
|
|
7
21
|
})
|
|
@@ -9,17 +23,6 @@ function run(args) {
|
|
|
9
23
|
try { return JSON.parse(r.stdout) } catch { return null }
|
|
10
24
|
}
|
|
11
25
|
|
|
12
|
-
function runLocal(args) {
|
|
13
|
-
try {
|
|
14
|
-
const r = spawnSync('ccusage', [...args, '--json'], {
|
|
15
|
-
timeout: 30_000, maxBuffer: 16 * 1024 * 1024, encoding: 'utf8',
|
|
16
|
-
env: { ...process.env, NO_COLOR: '1' },
|
|
17
|
-
})
|
|
18
|
-
if (r.status !== 0 || !r.stdout) return null
|
|
19
|
-
return JSON.parse(r.stdout)
|
|
20
|
-
} catch { return null }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
26
|
// Normalise the two very different per-agent response shapes into one schema.
|
|
24
27
|
function normalizeEntries(entries, agentId) {
|
|
25
28
|
return (entries || []).map(d => {
|
|
@@ -87,22 +90,22 @@ export async function getUsageData({ since, until, agent } = {}) {
|
|
|
87
90
|
|
|
88
91
|
let raw
|
|
89
92
|
if (agent && agent !== 'all') {
|
|
90
|
-
// Per-agent command:
|
|
93
|
+
// Per-agent command shape: ccusage claude daily --json
|
|
91
94
|
const agentArgs = [agent, 'daily', ...dateArgs]
|
|
92
|
-
raw =
|
|
95
|
+
raw = run(agentArgs)
|
|
93
96
|
if (!raw) {
|
|
94
97
|
// Fallback: run global and filter by metadata.agents
|
|
95
98
|
const globalArgs = ['daily', ...dateArgs]
|
|
96
|
-
const g =
|
|
99
|
+
const g = run(globalArgs)
|
|
97
100
|
if (g && Array.isArray(g.daily)) {
|
|
98
101
|
raw = { daily: g.daily.filter(d => (d.metadata?.agents || []).includes(agent)) }
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
} else {
|
|
102
|
-
raw =
|
|
105
|
+
raw = run(['daily', ...dateArgs])
|
|
103
106
|
}
|
|
104
107
|
|
|
105
|
-
if (!raw) return { available: false, error: 'ccusage
|
|
108
|
+
if (!raw) return { available: false, error: 'Bundled ccusage is unavailable or could not read local usage data' }
|
|
106
109
|
|
|
107
110
|
const entries = normalizeEntries(raw.daily || (Array.isArray(raw) ? raw : []), agent || 'all')
|
|
108
111
|
entries.sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0))
|