tokens-metric 0.3.3 → 0.3.5
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/dist/core/updater.js +65 -0
- package/dist/statusline/index.js +0 -0
- package/dist/tui/index.js +12 -3
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const CACHE_PATH = () => join(homedir(), '.tokens-metric', 'update-check.json');
|
|
5
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
6
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/tokens-metric/latest';
|
|
7
|
+
function readCache() {
|
|
8
|
+
try {
|
|
9
|
+
const raw = readFileSync(CACHE_PATH(), 'utf8');
|
|
10
|
+
const c = JSON.parse(raw);
|
|
11
|
+
if (typeof c.checkedAt !== 'number' || typeof c.latestVersion !== 'string')
|
|
12
|
+
return null;
|
|
13
|
+
return c;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function writeCache(latestVersion) {
|
|
20
|
+
try {
|
|
21
|
+
const dir = join(homedir(), '.tokens-metric');
|
|
22
|
+
mkdirSync(dir, { recursive: true });
|
|
23
|
+
writeFileSync(CACHE_PATH(), JSON.stringify({ checkedAt: Date.now(), latestVersion }), 'utf8');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// ignore
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isNewer(latest, current) {
|
|
30
|
+
const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
|
|
31
|
+
const [lMaj, lMin, lPat] = parse(latest);
|
|
32
|
+
const [cMaj, cMin, cPat] = parse(current);
|
|
33
|
+
if (lMaj !== cMaj)
|
|
34
|
+
return (lMaj ?? 0) > (cMaj ?? 0);
|
|
35
|
+
if (lMin !== cMin)
|
|
36
|
+
return (lMin ?? 0) > (cMin ?? 0);
|
|
37
|
+
return (lPat ?? 0) > (cPat ?? 0);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Checks npm registry for a newer version. Non-blocking — uses a 24h cache
|
|
41
|
+
* so most startups skip the network call entirely.
|
|
42
|
+
*
|
|
43
|
+
* Returns the latest version string if an update is available, null otherwise.
|
|
44
|
+
*/
|
|
45
|
+
export async function checkForUpdate(currentVersion) {
|
|
46
|
+
try {
|
|
47
|
+
const cached = readCache();
|
|
48
|
+
let latestVersion;
|
|
49
|
+
if (cached && Date.now() - cached.checkedAt < CACHE_TTL_MS) {
|
|
50
|
+
latestVersion = cached.latestVersion;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(5_000) });
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
return null;
|
|
56
|
+
const data = await res.json();
|
|
57
|
+
latestVersion = data.version;
|
|
58
|
+
writeCache(latestVersion);
|
|
59
|
+
}
|
|
60
|
+
return isNewer(latestVersion, currentVersion) ? latestVersion : null;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/statusline/index.js
CHANGED
|
File without changes
|
package/dist/tui/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { totalTokens } from '../core/types.js';
|
|
|
11
11
|
import { anonymizePath } from '../core/privacy.js';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { HELP_TEXT, parseArgs } from '../core/args.js';
|
|
14
|
+
import { checkForUpdate } from '../core/updater.js';
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
const pkg = require('../../package.json');
|
|
16
17
|
import { bar, sparklineCells } from './bars.js';
|
|
@@ -32,6 +33,7 @@ function App() {
|
|
|
32
33
|
const [now, setNow] = useState(Date.now());
|
|
33
34
|
const [lastTailAt, setLastTailAt] = useState(null);
|
|
34
35
|
const [history, setHistory] = useState(null);
|
|
36
|
+
const [updateAvailable, setUpdateAvailable] = useState(null);
|
|
35
37
|
const startedAtRef = useRef(Date.now());
|
|
36
38
|
const lastTotalRef = useRef(0);
|
|
37
39
|
// useInput requires raw mode (interactive TTY). Skip it when stdin is piped
|
|
@@ -45,6 +47,13 @@ function App() {
|
|
|
45
47
|
exit();
|
|
46
48
|
});
|
|
47
49
|
}
|
|
50
|
+
// Check for newer version on npm — non-blocking, result cached 24h.
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
checkForUpdate(pkg.version)
|
|
53
|
+
.then((v) => { if (v)
|
|
54
|
+
setUpdateAvailable(v); })
|
|
55
|
+
.catch(() => undefined);
|
|
56
|
+
}, []);
|
|
48
57
|
// Historical aggregate over all transcripts. Refreshed on an interval —
|
|
49
58
|
// the live session is already shown in SessionPanel, so minute-grain
|
|
50
59
|
// accuracy on today's bucket is enough and keeps full parses out of the
|
|
@@ -125,15 +134,15 @@ function App() {
|
|
|
125
134
|
const today = countToday(allTranscripts, now);
|
|
126
135
|
const ratePerSec = series.reduce((a, b) => a + b, 0) / SPARK_WIDTH;
|
|
127
136
|
const todaySessions = getTodaySessions(now, transcriptPath);
|
|
128
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Header, { auth: auth, sessionsToday: today.sessions, projectsToday: today.projects, lastTailAt: lastTailAt, startedAt: startedAtRef.current, now: now }), _jsxs(Box, { marginTop: 1, flexDirection: "row", gap: 1, children: [_jsx(SessionPanel, { stats: stats, ratePerSec: ratePerSec, now: now, series: series }), _jsx(BreakdownPanel, { stats: stats })] }), _jsx(Box, { marginTop: 1, children: _jsx(HistoryPanel, { history: history }) }), todaySessions.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsx(TodaySessionsPanel, { sessions: todaySessions, now: now }) })), _jsx(Box, { marginTop: 1, children: _jsx(TranscriptsPanel, { transcripts: transcripts, activePath: transcriptPath, now: now }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "magenta", children: "q" }), " quit \u00B7", ' ', _jsx(Text, { color: "magenta", children: "live" }), " tail of ~/.claude/projects \u00B7 pricing is", ' ', _jsx(Text, { italic: true, children: "API-equivalent" }), ", not your real bill on a subscription"] }) })] }));
|
|
137
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Header, { auth: auth, sessionsToday: today.sessions, projectsToday: today.projects, lastTailAt: lastTailAt, startedAt: startedAtRef.current, now: now, updateAvailable: updateAvailable }), _jsxs(Box, { marginTop: 1, flexDirection: "row", gap: 1, children: [_jsx(SessionPanel, { stats: stats, ratePerSec: ratePerSec, now: now, series: series }), _jsx(BreakdownPanel, { stats: stats })] }), _jsx(Box, { marginTop: 1, children: _jsx(HistoryPanel, { history: history }) }), todaySessions.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsx(TodaySessionsPanel, { sessions: todaySessions, now: now }) })), _jsx(Box, { marginTop: 1, children: _jsx(TranscriptsPanel, { transcripts: transcripts, activePath: transcriptPath, now: now }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "magenta", children: "q" }), " quit \u00B7", ' ', _jsx(Text, { color: "magenta", children: "live" }), " tail of ~/.claude/projects \u00B7 pricing is", ' ', _jsx(Text, { italic: true, children: "API-equivalent" }), ", not your real bill on a subscription"] }) })] }));
|
|
129
138
|
}
|
|
130
139
|
// ── Header ───────────────────────────────────────────────────────────────────
|
|
131
|
-
function Header({ auth, sessionsToday, projectsToday, lastTailAt, startedAt, now, }) {
|
|
140
|
+
function Header({ auth, sessionsToday, projectsToday, lastTailAt, startedAt, now, updateAvailable, }) {
|
|
132
141
|
const ok = auth.installed && auth.loggedIn;
|
|
133
142
|
const dot = ok ? 'green' : auth.installed ? 'yellow' : 'red';
|
|
134
143
|
const tailLabel = lastTailAt ? `updated ${timeAgo(now - lastTailAt)} ago` : 'waiting…';
|
|
135
144
|
const tailColor = !lastTailAt ? 'gray' : now - lastTailAt < 10_000 ? 'green' : 'yellow';
|
|
136
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "\u258C tokens-metric " }), _jsxs(Text, { dimColor: true, children: ["v", pkg.version, " \u2014 real-time Claude Code usage"] })] }), _jsx(Box, { marginTop: 0, children: _jsxs(Text, { children: [_jsx(Text, { color: dot, children: "\u25CF" }), ' ', auth.installed ? 'Claude Code detected' : 'Claude Code NOT detected', _jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: sessionsToday }), _jsx(Text, { dimColor: true, children: ` ${plural(sessionsToday, 'session', 'sessions')} · ` }), _jsx(Text, { children: projectsToday }), _jsx(Text, { dimColor: true, children: ` ${plural(projectsToday, 'project', 'projects')} today` })] }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "watching ~/.claude/projects \u00B7 " }), _jsx(Text, { color: tailColor, children: tailLabel }), _jsx(Text, { dimColor: true, children: ' · uptime ' }), _jsx(Text, { children: timeAgo(now - startedAt) })] })] }));
|
|
145
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "\u258C tokens-metric " }), _jsxs(Text, { dimColor: true, children: ["v", pkg.version, " \u2014 real-time Claude Code usage"] })] }), _jsx(Box, { marginTop: 0, children: _jsxs(Text, { children: [_jsx(Text, { color: dot, children: "\u25CF" }), ' ', auth.installed ? 'Claude Code detected' : 'Claude Code NOT detected', _jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: sessionsToday }), _jsx(Text, { dimColor: true, children: ` ${plural(sessionsToday, 'session', 'sessions')} · ` }), _jsx(Text, { children: projectsToday }), _jsx(Text, { dimColor: true, children: ` ${plural(projectsToday, 'project', 'projects')} today` })] }) }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "watching ~/.claude/projects \u00B7 " }), _jsx(Text, { color: tailColor, children: tailLabel }), _jsx(Text, { dimColor: true, children: ' · uptime ' }), _jsx(Text, { children: timeAgo(now - startedAt) })] }), updateAvailable && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "\u26A1 Update available: " }), _jsxs(Text, { dimColor: true, children: ["v", pkg.version] }), _jsx(Text, { color: "yellow", children: " \u2192 " }), _jsxs(Text, { color: "yellow", bold: true, children: ["v", updateAvailable] }), _jsx(Text, { dimColor: true, children: " npm install -g tokens-metric" })] }))] }));
|
|
137
146
|
}
|
|
138
147
|
function countToday(transcripts, now) {
|
|
139
148
|
const startOfDay = new Date(now);
|