Skip to content

Instantly share code, notes, and snippets.

@ehfeng
Created May 4, 2026 18:42
Show Gist options
  • Select an option

  • Save ehfeng/1ddb18d6116205cd19f2fe781f23a13f to your computer and use it in GitHub Desktop.

Select an option

Save ehfeng/1ddb18d6116205cd19f2fe781f23a13f to your computer and use it in GitHub Desktop.
Kernel session usage export CLI — queries the Kernel API for past browser sessions and outputs CSV

Session Usage Export

CLI tool that queries the Kernel API for past browser sessions and exports them to CSV.

Setup

npm install

Usage

# With API key in env
export KERNEL_API_KEY=your_api_key
npx tsx index.ts

# Or it will prompt you for the key
npx tsx index.ts

The CLI prompts for a start and end date (YYYY-MM-DD format), then fetches all sessions in that range and writes a CSV file named sessions_<start>_<end>.csv.

CSV Columns

Column Description
session_id Unique session identifier
created_at ISO timestamp when the session was created
deleted_at ISO timestamp when the session was deleted (empty if still active)
usage_time Human-readable usage duration (e.g. 0h 5m 30s) or active if still running
usage_ms Usage time in milliseconds
pool_id Browser pool ID (empty if not from a pool)
pool_name Browser pool name (empty if unnamed or not from a pool)
browser_type One of: headful, headless, gpu

Output

CSV is written to a file. Progress is printed to stderr so you can pipe stdout if needed.

import Kernel from "@onkernel/sdk";
import { createInterface } from "node:readline";
import { createWriteStream } from "node:fs";
const rl = createInterface({
input: process.stdin,
output: process.stderr,
});
function ask(question: string): Promise<string> {
return new Promise((resolve) => rl.question(question, resolve));
}
function parseDate(input: string): Date {
const d = new Date(input);
if (isNaN(d.getTime())) {
console.error(`Invalid date: ${input}`);
process.exit(1);
}
return d;
}
function getBrowserType(headless: boolean, gpu?: boolean): string {
if (headless) return "headless";
if (gpu) return "gpu";
return "headful";
}
function formatMs(ms: number): string {
const totalSeconds = Math.floor(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
return `${hours}h ${minutes}m ${seconds}s`;
}
function escapeCSV(value: string): string {
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
}
async function main() {
let apiKey = process.env.KERNEL_API_KEY;
if (!apiKey) {
apiKey = await ask("Enter your Kernel API key: ");
if (!apiKey.trim()) {
console.error("API key is required.");
process.exit(1);
}
}
const startInput = await ask("Start date (YYYY-MM-DD): ");
const startDate = parseDate(startInput);
const endInput = await ask("End date (YYYY-MM-DD): ");
const endDate = parseDate(endInput);
endDate.setHours(23, 59, 59, 999);
if (endDate < startDate) {
console.error("End date must be after start date.");
process.exit(1);
}
rl.close();
const client = new Kernel({ apiKey });
const outputFile = `sessions_${startInput}_${endInput}.csv`;
const ws = createWriteStream(outputFile);
const columns = [
"session_id",
"created_at",
"deleted_at",
"usage_time",
"usage_ms",
"pool_id",
"pool_name",
"browser_type",
];
ws.write(columns.join(",") + "\n");
console.error(`Fetching sessions from ${startInput} to ${endInput}...`);
let count = 0;
let scanned = 0;
for await (const session of client.browsers.list({
status: "all",
limit: 100,
})) {
scanned++;
const createdAt = new Date(session.created_at);
// Sessions are returned newest-first. Once we pass the start date, stop.
if (createdAt < startDate) break;
if (createdAt > endDate) continue;
const uptimeMs = session.usage?.uptime_ms ?? 0;
const row = [
escapeCSV(session.session_id),
escapeCSV(session.created_at),
escapeCSV(session.deleted_at ?? ""),
escapeCSV(uptimeMs > 0 ? formatMs(uptimeMs) : "active"),
String(uptimeMs),
escapeCSV(session.pool?.id ?? ""),
escapeCSV(session.pool?.name ?? ""),
escapeCSV(getBrowserType(session.headless, session.gpu)),
];
ws.write(row.join(",") + "\n");
count++;
if (scanned % 500 === 0) {
console.error(` scanned ${scanned} sessions, matched ${count}...`);
}
}
ws.end();
console.error(
`\nDone. ${count} sessions written to ${outputFile} (scanned ${scanned}).`
);
}
main().catch((err) => {
console.error("Error:", err.message ?? err);
process.exit(1);
});
{
"name": "session-usage-export",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "npx tsx index.ts"
},
"dependencies": {
"@onkernel/sdk": "^0.52.0"
},
"devDependencies": {
"@types/node": "^25.6.0",
"tsx": "^4.19.0",
"typescript": "^5.5.0"
}
}
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["index.ts"]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment