@geoqiao/pi-usage 0.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/LICENSE +22 -0
- package/README.md +222 -0
- package/bin/pi-usage.js +69 -0
- package/data/models.dev-LICENSE +21 -0
- package/data/prices.json +2678 -0
- package/extensions/usage-report.js +36 -0
- package/package.json +51 -0
- package/src/analytics.js +189 -0
- package/src/collect.js +34 -0
- package/src/network.js +25 -0
- package/src/report.js +43 -0
- package/vendor/vibe-usage/NOTICE.md +58 -0
- package/vendor/vibe-usage/src/cindy-roots.js +85 -0
- package/vendor/vibe-usage/src/claude-roots.js +165 -0
- package/vendor/vibe-usage/src/cline-roots.js +40 -0
- package/vendor/vibe-usage/src/codex-roots.js +46 -0
- package/vendor/vibe-usage/src/craft-roots.js +15 -0
- package/vendor/vibe-usage/src/extra-roots.js +312 -0
- package/vendor/vibe-usage/src/parsers/aggregate.js +196 -0
- package/vendor/vibe-usage/src/parsers/alma.js +94 -0
- package/vendor/vibe-usage/src/parsers/amp.js +156 -0
- package/vendor/vibe-usage/src/parsers/antigravity-db.js +359 -0
- package/vendor/vibe-usage/src/parsers/antigravity.js +530 -0
- package/vendor/vibe-usage/src/parsers/cindy-ledger.js +157 -0
- package/vendor/vibe-usage/src/parsers/claude-code.js +372 -0
- package/vendor/vibe-usage/src/parsers/cline.js +92 -0
- package/vendor/vibe-usage/src/parsers/codex-cache.js +138 -0
- package/vendor/vibe-usage/src/parsers/codex.js +1198 -0
- package/vendor/vibe-usage/src/parsers/contract.js +55 -0
- package/vendor/vibe-usage/src/parsers/copilot-cli.js +128 -0
- package/vendor/vibe-usage/src/parsers/craft-agent.js +21 -0
- package/vendor/vibe-usage/src/parsers/cursor.js +262 -0
- package/vendor/vibe-usage/src/parsers/dimagent.js +127 -0
- package/vendor/vibe-usage/src/parsers/droid.js +113 -0
- package/vendor/vibe-usage/src/parsers/dsh.js +563 -0
- package/vendor/vibe-usage/src/parsers/fs-utils.js +36 -0
- package/vendor/vibe-usage/src/parsers/gemini-cli.js +190 -0
- package/vendor/vibe-usage/src/parsers/grok.js +395 -0
- package/vendor/vibe-usage/src/parsers/hermes.js +123 -0
- package/vendor/vibe-usage/src/parsers/index.js +61 -0
- package/vendor/vibe-usage/src/parsers/kimi-code.js +467 -0
- package/vendor/vibe-usage/src/parsers/kiro.js +788 -0
- package/vendor/vibe-usage/src/parsers/mcode.js +182 -0
- package/vendor/vibe-usage/src/parsers/mimocode.js +88 -0
- package/vendor/vibe-usage/src/parsers/omp.js +10 -0
- package/vendor/vibe-usage/src/parsers/openclaw.js +142 -0
- package/vendor/vibe-usage/src/parsers/opencode.js +151 -0
- package/vendor/vibe-usage/src/parsers/pi-coding-agent.js +27 -0
- package/vendor/vibe-usage/src/parsers/pi-session-jsonl.js +166 -0
- package/vendor/vibe-usage/src/parsers/qwen-code.js +122 -0
- package/vendor/vibe-usage/src/parsers/roo-code.js +123 -0
- package/vendor/vibe-usage/src/parsers/sqlite.js +148 -0
- package/vendor/vibe-usage/src/parsers/trae-cli.js +171 -0
- package/vendor/vibe-usage/src/parsers/workbuddy.js +322 -0
- package/vendor/vibe-usage/src/parsers/zcode.js +115 -0
- package/vendor/vibe-usage/src/pi-roots.js +125 -0
- package/vendor/vibe-usage/src/tools.js +422 -0
- package/vendor/vibe-usage/src/workbuddy-roots.js +22 -0
- package/vendor/vibe-usage/upstream-files.json +48 -0
- package/web/report.css +10 -0
- package/web/report.html +81 -0
- package/web/report.js +310 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { delimiter, isAbsolute, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { piSessionsDir } from './extra-roots.js';
|
|
5
|
+
|
|
6
|
+
function expandHome(value) {
|
|
7
|
+
const trimmed = value.trim();
|
|
8
|
+
if (trimmed === '~') return homedir();
|
|
9
|
+
if (trimmed.startsWith('~/') || trimmed.startsWith('~\\')) {
|
|
10
|
+
return join(homedir(), trimmed.slice(2));
|
|
11
|
+
}
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function uniqueExistingDirs(paths) {
|
|
16
|
+
return [...new Set(paths.map(expandHome))].filter(existsSync);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function profileSessionDirs(profilesRoot, includesAgentDir) {
|
|
20
|
+
const dirs = [];
|
|
21
|
+
let profiles;
|
|
22
|
+
try {
|
|
23
|
+
profiles = readdirSync(profilesRoot, { withFileTypes: true });
|
|
24
|
+
} catch {
|
|
25
|
+
return dirs;
|
|
26
|
+
}
|
|
27
|
+
for (const profile of profiles) {
|
|
28
|
+
if (!profile.isDirectory()) continue;
|
|
29
|
+
dirs.push(join(
|
|
30
|
+
profilesRoot,
|
|
31
|
+
profile.name,
|
|
32
|
+
...(includesAgentDir ? ['agent', 'sessions'] : ['sessions']),
|
|
33
|
+
));
|
|
34
|
+
}
|
|
35
|
+
return dirs;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function looksLikeOmpAgentDir(agentDir) {
|
|
39
|
+
const normalized = agentDir.replace(/\\/g, '/');
|
|
40
|
+
return normalized.includes('/.omp/')
|
|
41
|
+
|| existsSync(join(agentDir, 'config.yml'))
|
|
42
|
+
|| existsSync(join(agentDir, 'agent.db'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Pi resolves a session directory from `--session-dir`, then
|
|
46
|
+
// PI_CODING_AGENT_SESSION_DIR, then `sessionDir` in settings.json. Only the
|
|
47
|
+
// last two are discoverable after the fact, and both name the sessions
|
|
48
|
+
// directory itself (no `sessions` segment is appended).
|
|
49
|
+
function settingsSessionDir(agentDir) {
|
|
50
|
+
try {
|
|
51
|
+
const raw = readFileSync(join(agentDir, 'settings.json'), 'utf-8');
|
|
52
|
+
const value = JSON.parse(raw)?.sessionDir;
|
|
53
|
+
if (typeof value !== 'string') return null;
|
|
54
|
+
const trimmed = value.trim();
|
|
55
|
+
// Pi also accepts project-relative values. Those resolve against a cwd we
|
|
56
|
+
// do not have here, so only absolute and ~-anchored paths are scanned.
|
|
57
|
+
if (!trimmed || !(isAbsolute(trimmed) || trimmed.startsWith('~'))) return null;
|
|
58
|
+
return expandHome(trimmed);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getPiSessionDirs(extraRoots = []) {
|
|
65
|
+
// Explicitly configured roots are user intent, not a fixture: they are always
|
|
66
|
+
// scanned, and they never replace the default store. A root whose shape no
|
|
67
|
+
// longer resolves drops out here; the parser reports that as skipped.
|
|
68
|
+
const extraDirs = extraRoots.map(piSessionsDir).filter(dir => dir !== null);
|
|
69
|
+
|
|
70
|
+
const override = process.env.VIBE_USAGE_PI_SESSION_DIRS?.trim();
|
|
71
|
+
if (override) return uniqueExistingDirs([...override.split(delimiter), ...extraDirs]);
|
|
72
|
+
|
|
73
|
+
const envAgentDir = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
74
|
+
const agentDir = envAgentDir ? expandHome(envAgentDir) : join(homedir(), '.pi', 'agent');
|
|
75
|
+
// OMP inherits PI_CODING_AGENT_DIR from Pi. Do not parse an identifiable
|
|
76
|
+
// OMP store again as source=pi-coding-agent.
|
|
77
|
+
const isOmpStore = Boolean(envAgentDir) && looksLikeOmpAgentDir(agentDir);
|
|
78
|
+
|
|
79
|
+
const dirs = [];
|
|
80
|
+
if (!isOmpStore) {
|
|
81
|
+
dirs.push(join(agentDir, 'sessions'));
|
|
82
|
+
const envSessionDir = process.env.PI_CODING_AGENT_SESSION_DIR?.trim();
|
|
83
|
+
if (envSessionDir) dirs.push(expandHome(envSessionDir));
|
|
84
|
+
const configured = settingsSessionDir(agentDir);
|
|
85
|
+
if (configured) dirs.push(configured);
|
|
86
|
+
}
|
|
87
|
+
return uniqueExistingDirs([...dirs, ...extraDirs]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getOmpSessionDirs() {
|
|
91
|
+
const override = process.env.VIBE_USAGE_OMP_SESSION_DIRS?.trim();
|
|
92
|
+
if (override) return uniqueExistingDirs(override.split(delimiter));
|
|
93
|
+
|
|
94
|
+
const dirs = [];
|
|
95
|
+
const configName = process.env.PI_CONFIG_DIR?.trim() || '.omp';
|
|
96
|
+
const configRoot = join(homedir(), configName);
|
|
97
|
+
dirs.push(join(configRoot, 'agent', 'sessions'));
|
|
98
|
+
for (const dir of profileSessionDirs(join(configRoot, 'profiles'), true)) {
|
|
99
|
+
dirs.push(dir);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const agentOverride = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
103
|
+
if (agentOverride) {
|
|
104
|
+
const expanded = expandHome(agentOverride);
|
|
105
|
+
if (looksLikeOmpAgentDir(expanded)) dirs.push(join(expanded, 'sessions'));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// OMP's XDG migration flattens the agent/ segment:
|
|
109
|
+
// ~/.omp/agent/sessions -> $XDG_DATA_HOME/omp/sessions.
|
|
110
|
+
if (process.platform === 'linux' || process.platform === 'darwin') {
|
|
111
|
+
const xdgDataHome = process.env.XDG_DATA_HOME?.trim();
|
|
112
|
+
if (xdgDataHome) {
|
|
113
|
+
const xdgRoot = join(expandHome(xdgDataHome), 'omp');
|
|
114
|
+
dirs.push(join(xdgRoot, 'sessions'));
|
|
115
|
+
for (const dir of profileSessionDirs(join(xdgRoot, 'profiles'), false)) {
|
|
116
|
+
dirs.push(dir);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return uniqueExistingDirs(dirs);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const findPiDataDirs = getPiSessionDirs;
|
|
125
|
+
export const findOmpDataDirs = getOmpSessionDirs;
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { dirname, isAbsolute, join, posix, resolve, win32 } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { findClaudeCodeDataDirs } from './claude-roots.js';
|
|
5
|
+
import { findCindyDataDirs, getCindyDataRoots } from './cindy-roots.js';
|
|
6
|
+
import { codexSessionDirs, resolveCodexHomes } from './codex-roots.js';
|
|
7
|
+
import {
|
|
8
|
+
antigravityConversationDirs,
|
|
9
|
+
discoverCodexHomes,
|
|
10
|
+
extraRootList,
|
|
11
|
+
grokSessionsDir,
|
|
12
|
+
} from './extra-roots.js';
|
|
13
|
+
import { findClineDataDirs } from './cline-roots.js';
|
|
14
|
+
import { findCraftDataDirs } from './craft-roots.js';
|
|
15
|
+
import { findOmpDataDirs, findPiDataDirs } from './pi-roots.js';
|
|
16
|
+
import { findWorkbuddyDataDirs } from './workbuddy-roots.js';
|
|
17
|
+
|
|
18
|
+
export function getAlmaDbPath(env = process.env, platform = process.platform, home = homedir()) {
|
|
19
|
+
const pathImpl = platform === 'win32' ? win32 : posix;
|
|
20
|
+
const override = env.VIBE_USAGE_ALMA_DB?.trim();
|
|
21
|
+
if (override) {
|
|
22
|
+
return platform === process.platform ? resolve(override) : pathImpl.resolve(override);
|
|
23
|
+
}
|
|
24
|
+
if (platform === 'darwin') {
|
|
25
|
+
return pathImpl.join(home, 'Library', 'Application Support', 'alma', 'chat_threads.db');
|
|
26
|
+
}
|
|
27
|
+
if (platform === 'win32') {
|
|
28
|
+
const appData = env.APPDATA?.trim() || pathImpl.join(home, 'AppData', 'Roaming');
|
|
29
|
+
return pathImpl.join(appData, 'alma', 'chat_threads.db');
|
|
30
|
+
}
|
|
31
|
+
const configHome = env.XDG_CONFIG_HOME?.trim() || pathImpl.join(home, '.config');
|
|
32
|
+
return pathImpl.join(configHome, 'alma', 'chat_threads.db');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getCursorStateDbPath() {
|
|
36
|
+
const rel = join('User', 'globalStorage', 'state.vscdb');
|
|
37
|
+
if (process.platform === 'darwin') {
|
|
38
|
+
return join(homedir(), 'Library', 'Application Support', 'Cursor', rel);
|
|
39
|
+
}
|
|
40
|
+
if (process.platform === 'win32') {
|
|
41
|
+
const appData = process.env.APPDATA?.trim() || join(homedir(), 'AppData', 'Roaming');
|
|
42
|
+
return join(appData, 'Cursor', rel);
|
|
43
|
+
}
|
|
44
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), '.config');
|
|
45
|
+
return join(xdgConfigHome, 'Cursor', rel);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getKiroAgentPath() {
|
|
49
|
+
const rel = join('User', 'globalStorage', 'kiro.kiroagent');
|
|
50
|
+
if (process.platform === 'darwin') {
|
|
51
|
+
return join(homedir(), 'Library', 'Application Support', 'Kiro', rel);
|
|
52
|
+
}
|
|
53
|
+
if (process.platform === 'win32') {
|
|
54
|
+
const appData = process.env.APPDATA?.trim() || join(homedir(), 'AppData', 'Roaming');
|
|
55
|
+
return join(appData, 'Kiro', rel);
|
|
56
|
+
}
|
|
57
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), '.config');
|
|
58
|
+
return join(xdgConfigHome, 'Kiro', rel);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// VSCode-fork host directories where extensions like Cline / Roo Code live.
|
|
62
|
+
const VSCODE_HOSTS = ['Code', 'Cursor', 'Windsurf', 'VSCodium', 'Code - Insiders', 'Trae', 'Trae CN'];
|
|
63
|
+
|
|
64
|
+
function getVscodeHostRoots() {
|
|
65
|
+
const out = [];
|
|
66
|
+
if (process.platform === 'darwin') {
|
|
67
|
+
const base = join(homedir(), 'Library', 'Application Support');
|
|
68
|
+
for (const h of VSCODE_HOSTS) out.push(join(base, h));
|
|
69
|
+
} else if (process.platform === 'win32') {
|
|
70
|
+
const appData = process.env.APPDATA?.trim() || join(homedir(), 'AppData', 'Roaming');
|
|
71
|
+
for (const h of VSCODE_HOSTS) out.push(join(appData, h));
|
|
72
|
+
} else {
|
|
73
|
+
const xdg = process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), '.config');
|
|
74
|
+
for (const h of VSCODE_HOSTS) out.push(join(xdg, h));
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findExtensionDirs(extensionId) {
|
|
80
|
+
const dirs = [];
|
|
81
|
+
for (const root of getVscodeHostRoots()) {
|
|
82
|
+
const ext = join(root, 'User', 'globalStorage', extensionId);
|
|
83
|
+
try {
|
|
84
|
+
if (statSync(ext).isDirectory()) dirs.push(ext);
|
|
85
|
+
} catch {
|
|
86
|
+
// not present in this host
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return dirs;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const findRooCodeDataDirs = () => findExtensionDirs('rooveterinaryinc.roo-cline');
|
|
93
|
+
|
|
94
|
+
/** Find all OpenClaw data roots: ~/.openclaw and ~/.openclaw-<profile> */
|
|
95
|
+
function findOpenclawDataDirs() {
|
|
96
|
+
const home = homedir();
|
|
97
|
+
const dirs = [];
|
|
98
|
+
try {
|
|
99
|
+
for (const entry of readdirSync(home, { withFileTypes: true })) {
|
|
100
|
+
if (!entry.isDirectory()) continue;
|
|
101
|
+
if (entry.name === '.openclaw' || /^\.openclaw-.+/.test(entry.name)) {
|
|
102
|
+
const agentsDir = join(home, entry.name, 'agents');
|
|
103
|
+
if (existsSync(agentsDir)) dirs.push(agentsDir);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// ignore read errors
|
|
108
|
+
}
|
|
109
|
+
return dirs;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Codex keeps live sessions in ~/.codex/sessions and moves completed ones to
|
|
113
|
+
// ~/.codex/archived_sessions. Detect Codex if either dir exists, so a user
|
|
114
|
+
// whose sessions have all been archived is still recognized.
|
|
115
|
+
export function findCodexDataDirs(codexExtraHome, extraRoots = []) {
|
|
116
|
+
const configuredHomes = extraRoots.flatMap(root => discoverCodexHomes(root).homes);
|
|
117
|
+
return [...new Set([...resolveCodexHomes(codexExtraHome), ...configuredHomes])]
|
|
118
|
+
.flatMap(codexSessionDirs)
|
|
119
|
+
.filter(existsSync);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Kimi Code moved its store from ~/.kimi to ~/.kimi-code; recognize either so
|
|
123
|
+
// users on either version are detected. The parser prefers ~/.kimi-code.
|
|
124
|
+
function findKimiCodeDataDirs() {
|
|
125
|
+
return [
|
|
126
|
+
join(homedir(), '.kimi-code', 'sessions'),
|
|
127
|
+
join(homedir(), '.kimi', 'sessions'),
|
|
128
|
+
].filter(existsSync);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** DeepSeek Harness home: DSH_HOME env (same as the dsh CLI) or ~/.dsh. */
|
|
132
|
+
export function getDshHome(env = process.env) {
|
|
133
|
+
const explicit = env.DSH_HOME?.trim();
|
|
134
|
+
if (!explicit) return join(homedir(), '.dsh');
|
|
135
|
+
if (explicit === '~') return homedir();
|
|
136
|
+
if (explicit.startsWith('~/') || explicit.startsWith('~\\')) {
|
|
137
|
+
return resolve(homedir(), explicit.slice(2));
|
|
138
|
+
}
|
|
139
|
+
return resolve(explicit);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function getDshSessionsDir() {
|
|
143
|
+
const testDir = process.env.VIBE_USAGE_DSH_SESSIONS?.trim();
|
|
144
|
+
if (testDir) return testDir;
|
|
145
|
+
return join(getDshHome(), 'sessions');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Detect DeepSeek Harness when its sessions tree exists (or the test override).
|
|
149
|
+
export function findDshDataDirs() {
|
|
150
|
+
return [getDshSessionsDir()].filter(existsSync);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** mcode runtime database: VIBE_USAGE_MCODE_DB wins, then MCODE_HOME, then ~/.minimax. */
|
|
154
|
+
export function getMcodeDbPath(env = process.env, home = homedir()) {
|
|
155
|
+
const override = env.VIBE_USAGE_MCODE_DB?.trim();
|
|
156
|
+
if (override) return isAbsolute(override) ? override : resolve(override);
|
|
157
|
+
if (env.MCODE_HOME && !isAbsolute(env.MCODE_HOME)) {
|
|
158
|
+
throw new Error(`MCODE_HOME must be an absolute path, got: ${JSON.stringify(env.MCODE_HOME)}`);
|
|
159
|
+
}
|
|
160
|
+
const root = env.MCODE_HOME || join(home, '.minimax');
|
|
161
|
+
return join(root, 'v2', 'sqlite', 'runtime-state.sqlite');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function getMimocodeDbPath(env = process.env) {
|
|
165
|
+
if (env.MIMOCODE_HOME && !isAbsolute(env.MIMOCODE_HOME)) {
|
|
166
|
+
throw new Error(`MIMOCODE_HOME must be an absolute path, got: ${JSON.stringify(env.MIMOCODE_HOME)}`);
|
|
167
|
+
}
|
|
168
|
+
const dataDir = env.MIMOCODE_HOME
|
|
169
|
+
? join(env.MIMOCODE_HOME, 'data')
|
|
170
|
+
: join(env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'mimocode');
|
|
171
|
+
if (!env.MIMOCODE_DB) return join(dataDir, 'mimocode.db');
|
|
172
|
+
return isAbsolute(env.MIMOCODE_DB) ? env.MIMOCODE_DB : join(dataDir, env.MIMOCODE_DB);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function findAntigravityDataDirs(extraRoots = []) {
|
|
176
|
+
return [...new Set([
|
|
177
|
+
join(homedir(), '.gemini', 'antigravity'),
|
|
178
|
+
join(homedir(), '.gemini', 'antigravity-cli'),
|
|
179
|
+
...extraRoots.flatMap(root => antigravityConversationDirs(root).map(dirname)),
|
|
180
|
+
])].filter(existsSync);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function findTraeCliDataDirs() {
|
|
184
|
+
const envDir = process.env.VIBE_USAGE_TRAE_CLI_SESSIONS?.trim();
|
|
185
|
+
if (envDir) {
|
|
186
|
+
return [envDir].filter(existsSync);
|
|
187
|
+
}
|
|
188
|
+
if (process.platform === 'darwin') {
|
|
189
|
+
return [join(homedir(), 'Library', 'Caches', 'trae-cli', 'sessions')].filter(existsSync);
|
|
190
|
+
}
|
|
191
|
+
if (process.platform === 'win32') {
|
|
192
|
+
const localAppData = process.env.LOCALAPPDATA?.trim() || join(homedir(), 'AppData', 'Local');
|
|
193
|
+
return [join(localAppData, 'trae-cli', 'cache', 'sessions')].filter(existsSync);
|
|
194
|
+
}
|
|
195
|
+
const xdgCacheHome = process.env.XDG_CACHE_HOME?.trim() || join(homedir(), '.cache');
|
|
196
|
+
return [join(xdgCacheHome, 'trae-cli', 'sessions')].filter(existsSync);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Grok home: GROK_HOME env (same as the Grok CLI) or ~/.grok. */
|
|
200
|
+
export function getGrokHome() {
|
|
201
|
+
const envHome = process.env.GROK_HOME?.trim();
|
|
202
|
+
if (envHome) {
|
|
203
|
+
return envHome.startsWith('~') ? join(homedir(), envHome.slice(1)) : envHome;
|
|
204
|
+
}
|
|
205
|
+
return join(homedir(), '.grok');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function getGrokSessionsDir() {
|
|
209
|
+
const testDir = process.env.VIBE_USAGE_GROK_SESSIONS?.trim();
|
|
210
|
+
if (testDir) return testDir;
|
|
211
|
+
return join(getGrokHome(), 'sessions');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Detect Grok when sessions/ exists under GROK_HOME (or the test override).
|
|
215
|
+
export function findGrokDataDirs(extraRoots = []) {
|
|
216
|
+
const testDir = process.env.VIBE_USAGE_GROK_SESSIONS?.trim();
|
|
217
|
+
if (testDir) return [testDir].filter(existsSync);
|
|
218
|
+
return [...new Set([
|
|
219
|
+
join(getGrokHome(), 'sessions'),
|
|
220
|
+
...extraRoots.map(grokSessionsDir),
|
|
221
|
+
])].filter(existsSync);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getDimAgentDbPath() {
|
|
225
|
+
const override = process.env.VIBE_USAGE_DIMAGENT_DB?.trim();
|
|
226
|
+
if (override) return resolve(override);
|
|
227
|
+
|
|
228
|
+
const explicitHome = process.env.DIMCODE_HOME?.trim();
|
|
229
|
+
if (explicitHome) return join(resolve(explicitHome), 'dimcode.sqlite');
|
|
230
|
+
|
|
231
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
|
|
232
|
+
const home = xdgConfigHome
|
|
233
|
+
? resolve(xdgConfigHome, '.dimcode', 'v2')
|
|
234
|
+
: join(homedir(), '.dimcode', 'v2');
|
|
235
|
+
return join(home, 'dimcode.sqlite');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function findDimAgentDataDirs() {
|
|
239
|
+
return [getDimAgentDbPath()].filter(existsSync);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const TOOLS = [
|
|
243
|
+
{
|
|
244
|
+
name: 'Alma',
|
|
245
|
+
id: 'alma',
|
|
246
|
+
dataDir: getAlmaDbPath(),
|
|
247
|
+
detectDataDirs: () => [getAlmaDbPath()].filter(existsSync),
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'Cindy',
|
|
251
|
+
id: 'cindy',
|
|
252
|
+
dataDir: getCindyDataRoots()[0],
|
|
253
|
+
detectDataDirs: findCindyDataDirs,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: 'Claude Code',
|
|
257
|
+
id: 'claude-code',
|
|
258
|
+
dataDir: join(homedir(), '.claude', 'projects'),
|
|
259
|
+
detectDataDirs: findClaudeCodeDataDirs,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: 'Codex CLI',
|
|
263
|
+
id: 'codex',
|
|
264
|
+
dataDir: join(homedir(), '.codex', 'sessions'),
|
|
265
|
+
detectDataDirs: ({ codexExtraHome, extraRoots } = {}) => (
|
|
266
|
+
findCodexDataDirs(codexExtraHome, extraRootList(extraRoots?.codex))
|
|
267
|
+
),
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: 'Grok',
|
|
271
|
+
id: 'grok',
|
|
272
|
+
dataDir: join(homedir(), '.grok', 'sessions'),
|
|
273
|
+
detectDataDirs: ({ extraRoots } = {}) => findGrokDataDirs(extraRootList(extraRoots?.grok)),
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'GitHub Copilot CLI',
|
|
277
|
+
id: 'copilot-cli',
|
|
278
|
+
dataDir: join(homedir(), '.copilot', 'session-state'),
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'CraftAgent',
|
|
282
|
+
id: 'craft-agent',
|
|
283
|
+
dataDir: join(homedir(), '.craft-agent', 'workspaces'),
|
|
284
|
+
detectDataDirs: findCraftDataDirs,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'Cursor',
|
|
288
|
+
id: 'cursor',
|
|
289
|
+
dataDir: getCursorStateDbPath(),
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: 'DimAgent',
|
|
293
|
+
id: 'dimagent',
|
|
294
|
+
dataDir: getDimAgentDbPath(),
|
|
295
|
+
detectDataDirs: findDimAgentDataDirs,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'Gemini CLI',
|
|
299
|
+
id: 'gemini-cli',
|
|
300
|
+
dataDir: join(homedir(), '.gemini', 'tmp'),
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'OpenCode',
|
|
304
|
+
id: 'opencode',
|
|
305
|
+
dataDir: join(homedir(), '.local', 'share', 'opencode'),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'OpenClaw',
|
|
309
|
+
id: 'openclaw',
|
|
310
|
+
dataDir: join(homedir(), '.openclaw', 'agents'),
|
|
311
|
+
detectDataDirs: findOpenclawDataDirs,
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: 'Oh My Pi',
|
|
315
|
+
id: 'omp',
|
|
316
|
+
dataDir: join(homedir(), '.omp', 'agent', 'sessions'),
|
|
317
|
+
detectDataDirs: findOmpDataDirs,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'pi',
|
|
321
|
+
id: 'pi-coding-agent',
|
|
322
|
+
dataDir: join(homedir(), '.pi', 'agent', 'sessions'),
|
|
323
|
+
detectDataDirs: ({ extraRoots } = {}) => (
|
|
324
|
+
findPiDataDirs(extraRootList(extraRoots?.['pi-coding-agent']))
|
|
325
|
+
),
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: 'Qwen Code',
|
|
329
|
+
id: 'qwen-code',
|
|
330
|
+
dataDir: join(homedir(), '.qwen', 'tmp'),
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'Kimi Code',
|
|
334
|
+
id: 'kimi-code',
|
|
335
|
+
// Current layout is ~/.kimi-code/sessions; ~/.kimi/sessions is the legacy
|
|
336
|
+
// path. The parser reads whichever exists (preferring ~/.kimi-code).
|
|
337
|
+
dataDir: join(homedir(), '.kimi-code', 'sessions'),
|
|
338
|
+
detectDataDirs: findKimiCodeDataDirs,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'MiniMax Code',
|
|
342
|
+
id: 'mcode',
|
|
343
|
+
dataDir: join(homedir(), '.minimax', 'v2', 'sqlite', 'runtime-state.sqlite'),
|
|
344
|
+
detectDataDirs: () => [getMcodeDbPath()].filter(existsSync),
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'MiMoCode',
|
|
348
|
+
id: 'mimocode',
|
|
349
|
+
dataDir: join(homedir(), '.local', 'share', 'mimocode', 'mimocode.db'),
|
|
350
|
+
detectDataDirs: () => [getMimocodeDbPath()].filter(existsSync),
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'Amp',
|
|
354
|
+
id: 'amp',
|
|
355
|
+
dataDir: join(homedir(), '.local', 'share', 'amp', 'threads'),
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'Droid',
|
|
359
|
+
id: 'droid',
|
|
360
|
+
dataDir: join(homedir(), '.factory', 'sessions'),
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: 'DeepSeek Harness',
|
|
364
|
+
id: 'dsh',
|
|
365
|
+
dataDir: getDshSessionsDir(),
|
|
366
|
+
detectDataDirs: findDshDataDirs,
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: 'Antigravity',
|
|
370
|
+
id: 'antigravity',
|
|
371
|
+
dataDir: join(homedir(), '.gemini', 'antigravity'),
|
|
372
|
+
detectDataDirs: ({ extraRoots } = {}) => (
|
|
373
|
+
findAntigravityDataDirs(extraRootList(extraRoots?.antigravity))
|
|
374
|
+
),
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'Trae CLI',
|
|
378
|
+
id: 'trae-cli',
|
|
379
|
+
dataDir: join(homedir(), 'Library', 'Caches', 'trae-cli', 'sessions'),
|
|
380
|
+
detectDataDirs: findTraeCliDataDirs,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: 'Hermes',
|
|
384
|
+
id: 'hermes',
|
|
385
|
+
dataDir: join(homedir(), '.hermes', 'state.db'),
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'Kiro',
|
|
389
|
+
id: 'kiro',
|
|
390
|
+
dataDir: getKiroAgentPath(),
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'Cline',
|
|
394
|
+
id: 'cline',
|
|
395
|
+
dataDir: join(homedir(), '.cline'),
|
|
396
|
+
detectDataDirs: findClineDataDirs,
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: 'Roo Code',
|
|
400
|
+
id: 'roo-code',
|
|
401
|
+
dataDir: join(homedir(), 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline'),
|
|
402
|
+
detectDataDirs: findRooCodeDataDirs,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: 'WorkBuddy',
|
|
406
|
+
id: 'workbuddy',
|
|
407
|
+
dataDir: join(homedir(), '.workbuddy-ai', 'projects'),
|
|
408
|
+
detectDataDirs: () => findWorkbuddyDataDirs().filter(existsSync),
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: 'ZCode',
|
|
412
|
+
id: 'zcode',
|
|
413
|
+
dataDir: join(homedir(), '.zcode', 'cli', 'db', 'db.sqlite'),
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
export function detectInstalledTools(options = {}) {
|
|
418
|
+
return TOOLS.filter(t => {
|
|
419
|
+
if (t.detectDataDirs) return t.detectDataDirs(options).length > 0;
|
|
420
|
+
return existsSync(t.dataDir);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { delimiter, join } from 'node:path';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
|
|
4
|
+
export function getDefaultWorkbuddyProjectsDirs(home = homedir()) {
|
|
5
|
+
return [
|
|
6
|
+
join(home, '.workbuddy-ai', 'projects'),
|
|
7
|
+
join(home, '.workbuddy', 'projects'),
|
|
8
|
+
];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Fixture/relocation hook. Entries may name either the WorkBuddy home or its
|
|
12
|
+
// projects/ directory; the parser normalizes both forms.
|
|
13
|
+
export function findWorkbuddyDataDirs() {
|
|
14
|
+
const override = process.env.VIBE_USAGE_WORKBUDDY_DIRS?.trim();
|
|
15
|
+
if (!override) return getDefaultWorkbuddyProjectsDirs();
|
|
16
|
+
return [...new Set(
|
|
17
|
+
override
|
|
18
|
+
.split(delimiter)
|
|
19
|
+
.map(entry => entry.trim())
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
)];
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"src/cindy-roots.js": "90850fa12e082af9c544218e72a2f26554ff2715535218912c5e0c0ba1d9dcf7",
|
|
3
|
+
"src/claude-roots.js": "7c94967ec58af3446aefd58703595b5a8e4196420c29f08210d367677ecf4055",
|
|
4
|
+
"src/cline-roots.js": "909d684836c6a5b0e32967ebb7f9131638435360354a6605ec2e82f8d031393b",
|
|
5
|
+
"src/codex-roots.js": "ea020a53e3f066015efee818f53ee28f4ffe69cab8a1da3cfb75471afd6db136",
|
|
6
|
+
"src/craft-roots.js": "ee35624ea8b4cc81796a76aece1de1f9c0861933bb58afb7cbfedca9cf39fd85",
|
|
7
|
+
"src/extra-roots.js": "0f33ccc98bfc7e7609ac4b57f08ecbb64267a1f62809aa276ee5c4bb24f6b8f6",
|
|
8
|
+
"src/parsers/aggregate.js": "c264a8589bf674ea9b3bacbf8c7916bc6612b1e049d8d9ac955cef2ca1b4f142",
|
|
9
|
+
"src/parsers/alma.js": "966c292a96ba7363dd0a4516395c33922ee9913330e5e5ec4abe706daae159cc",
|
|
10
|
+
"src/parsers/amp.js": "04690f6938d20fa92fb1e0131a95b0ee072810d96b15b75eae152ded1543bef6",
|
|
11
|
+
"src/parsers/antigravity-db.js": "eb8cb26d98696bd8e0f5dc3f766e4cf9245a176aca5ec6484d2c0e26e6236c5d",
|
|
12
|
+
"src/parsers/antigravity.js": "e40c2857ee4ca9e16e2d74ae13ba02e689b72671ac260e1ebce1bc67f515d5dd",
|
|
13
|
+
"src/parsers/cindy-ledger.js": "0f2c1bd8d9d979f654170ea04bfa6990d67814cf5295885d88b923a0caf8a2a4",
|
|
14
|
+
"src/parsers/claude-code.js": "f55e6f1a0ff78188d53dc902e49ec638731366d7782f718222e9ab0c6ec170ec",
|
|
15
|
+
"src/parsers/cline.js": "699ccb850a30f0f60b5cd089ccc62e11ec13122f50b20d1fe59b97e286e11e46",
|
|
16
|
+
"src/parsers/codex-cache.js": "54f60b4dd4f8e0ef847182fcf2274221a5cb47062c14cf2cf69da52f7f01a037",
|
|
17
|
+
"src/parsers/codex.js": "628b3bafd88b2f09604d748728786520e7f629d2089f52b133aa179c2cb98fe3",
|
|
18
|
+
"src/parsers/contract.js": "370f6df9f2a67d2acbf0d256fda5bd07d6b40ea820398425b60f7c1a2edcdacf",
|
|
19
|
+
"src/parsers/copilot-cli.js": "5cfe8973fa8ad81bc485729f73a745e5e89109e5e5c2fbb4575a55c1f4642d93",
|
|
20
|
+
"src/parsers/craft-agent.js": "4f5aa43f8e06c86418c93664df76beb246f18cbac473a0e6f3fac57f9478566e",
|
|
21
|
+
"src/parsers/cursor.js": "12234757e1d8966d066b428878519b91eb833a4d120346f2efe9035a708422b3",
|
|
22
|
+
"src/parsers/dimagent.js": "f67f3fcf640d5711823ff5a8b05e775f434cfab2836eb955f1ef5435cad45d79",
|
|
23
|
+
"src/parsers/droid.js": "d575bc26a1776a5cae339fbbb570ca709371a326b35a9cdc8a59501db43b61bb",
|
|
24
|
+
"src/parsers/dsh.js": "1d47e443177bae6f10fce5433dc0543ce3868f1220bc7895734ba215498f8855",
|
|
25
|
+
"src/parsers/fs-utils.js": "1b6e8f38f097066a4b0492ad96492a94a50803ef9a28dead4cff3c2cc03a57da",
|
|
26
|
+
"src/parsers/gemini-cli.js": "38faa8b8ce910b74aed25e840c92007375a26f9b484672452cedfb0c34cdcdda",
|
|
27
|
+
"src/parsers/grok.js": "b626d841799a46de6709a3bd6ee4e6268c30a5572919739501d35c9213a340b6",
|
|
28
|
+
"src/parsers/hermes.js": "a10bd4b404f1ad1f3410d2468a0b5aef45962cf2fdf195fcb75edcdc8f57e82a",
|
|
29
|
+
"src/parsers/index.js": "ac8fb3db5e06848af08fa9ef8ff78ee258720fe369ebd785014ea080a2d75de9",
|
|
30
|
+
"src/parsers/kimi-code.js": "71c6c451d177920544c14466abf2b2517d55647bec3ed4760e733e774a55e38f",
|
|
31
|
+
"src/parsers/kiro.js": "57385b49f294bc31b73416a94c49e29e90cc232769c69c528ac40202e1e3438c",
|
|
32
|
+
"src/parsers/mcode.js": "a06202dae047280432e6bc203cb005db02be5b887ee91791286bc9e42d6b99b5",
|
|
33
|
+
"src/parsers/mimocode.js": "27d688d82dd7a48f0ce45784cda042f262478ad58accfd55af437a261191589f",
|
|
34
|
+
"src/parsers/omp.js": "f9dd2df3ad18dd486f0abcd1847724845a90dbc929ee6dcf6d8ee899d92c9827",
|
|
35
|
+
"src/parsers/openclaw.js": "174cfe9be79f2fd580de87ead69edb9a0c421cb5e76d8521d8f0a786cdd75b8b",
|
|
36
|
+
"src/parsers/opencode.js": "926bbf9361f4398b3005f63d33a9314271851e4c7f9f675c0d2cacab32cbfd94",
|
|
37
|
+
"src/parsers/pi-coding-agent.js": "6ef4664bf2fd6d1a97a43660bd4b3396b482dce9a556e3aab776ba2cb0fe103e",
|
|
38
|
+
"src/parsers/pi-session-jsonl.js": "af573ea6afd12c4ad042238f674dfae5ffbaedcf826f9099b175ad193cdf925d",
|
|
39
|
+
"src/parsers/qwen-code.js": "53d9548c2349315dd775a3aa6baa2c86e8e19927bf9d25b68ca89be468579cbe",
|
|
40
|
+
"src/parsers/roo-code.js": "a2cf28b2277db4e06953755c6e485eca97c6bcb3a04815620234091d834930d0",
|
|
41
|
+
"src/parsers/sqlite.js": "3c891655585f0d8dd823ed4d5bbb150ba3be620518bc1a7e362518139ddfc4bc",
|
|
42
|
+
"src/parsers/trae-cli.js": "4374e0efad0e152183e9624a19290872420e1ed8ac0eec1474fb7219e764c3a9",
|
|
43
|
+
"src/parsers/workbuddy.js": "e62d22974480f49fa87682d42d9457f06d915cbefb5d0250ea95461e4a343f81",
|
|
44
|
+
"src/parsers/zcode.js": "380128cf6720d880feb50a0120aadd470d01dc85a4a638dcf2bfc74b9aeacbb8",
|
|
45
|
+
"src/pi-roots.js": "a48f44634f9af719db7d07a11d2ec0d47c1a9414118639d03fdccf281f2d6f46",
|
|
46
|
+
"src/tools.js": "2556314b95433f2ed3735ffe9520c8d57191cb03d26a5f0dc23cc631224a96d0",
|
|
47
|
+
"src/workbuddy-roots.js": "2f95bfd4d608749d731ea60cec925dc2ebca355816fa1b266a557a8bd85f0cb5"
|
|
48
|
+
}
|
package/web/report.css
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
:root{color-scheme:light;--bg:#edf1f6;--panel:#fff;--ink:#24344b;--muted:#617188;--line:#e0e7ef;--navy:#192c46;--blue:#3978ca;--teal:#188c88;--violet:#8972bc;--amber:#b57826;--soft:#f6f8fb;font:13px -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}
|
|
2
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink)}[hidden]{display:none!important}button,input,select{font:inherit;color:inherit;max-width:100%;min-width:0}button,select,summary{cursor:pointer}button,input,select{border:1px solid var(--line);border-radius:5px;background:var(--panel);min-height:32px;padding:5px 9px}button:hover{background:#eaf1f9;border-color:#b4c9e2}button:disabled{opacity:.35;cursor:default}button,a,summary{touch-action:manipulation}:focus-visible{outline:3px solid #367fce;outline-offset:3px}a{color:inherit}svg{display:block}svg.icon{width:17px;height:17px;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:1.65;stroke-linecap:round;stroke-linejoin:round}span[data-icon]{display:inline-flex;align-items:center}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;flex-shrink:0;padding:7px;background:transparent}.icon-button.small{width:28px;height:28px;min-height:28px;padding:5px;border-color:transparent;color:var(--muted)}.text-button{display:inline-flex;align-items:center;gap:6px;border:0;background:transparent;color:var(--muted);padding:2px 4px;font-size:11px;min-height:26px}.skip{position:fixed;top:-100px;z-index:10;background:white;padding:12px}.skip:focus{top:8px}
|
|
3
|
+
.app-header{height:64px;padding:0 28px;background:var(--navy);color:#f2f6fc;display:flex;align-items:center;gap:40px}.brand{display:flex;align-items:center;gap:10px;text-decoration:none;font:600 19px "Avenir Next",Avenir,sans-serif;letter-spacing:-.4px}.brand-mark{font:italic 34px Georgia,serif;color:#9fc9ff}.brand-sub{display:block;font:8px "SFMono-Regular",Consolas,monospace;letter-spacing:1.7px;margin-top:2px;color:#a9bad0}.views{display:flex;align-self:stretch;gap:6px;flex:1}.views button{display:flex;align-items:center;gap:8px;border:0;border-radius:0;background:transparent;color:#aabbd0;padding:0 16px;border-bottom:3px solid transparent;font-size:12px}.views button:hover{color:white;background:#ffffff09}.views button[aria-pressed=true]{color:white;border-bottom-color:#86b7f9;background:#ffffff08}.app-header .actions{gap:6px}.app-header .icon-button{color:#c2d0e0;border-color:#ffffff1c}.app-header .icon-button:hover{background:#ffffff12}.app-header :focus-visible{outline-color:#afceff}main{max-width:1680px;margin:auto;padding:18px 28px 0}.actions{display:flex;align-items:center;gap:8px;min-width:0}.filters{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.date-filter{display:flex;gap:8px;align-items:center;padding:0 8px;background:white;border:1px solid var(--line);border-radius:5px}.date-filter>span{color:var(--muted)}.date-filter input{border:0;background:transparent;width:132px;padding-inline:2px;font-size:12px}.presets{display:flex;gap:3px}.presets button{border-color:transparent;background:transparent;color:var(--muted);font-size:11px;min-width:38px}.presets button[aria-pressed=true]{background:#dce7f5;color:#285994}.filter-spacer{flex:1}.dimension-filters{width:100%;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}.dimension-filters label{display:flex;align-items:center;gap:7px;min-width:0;background:white;border:1px solid var(--line);border-radius:5px;padding-left:10px;color:var(--muted);font-size:11px}.dimension-filters label>span{flex-shrink:0}.dimension-filters select{width:100%;border:0;background:transparent;color:var(--ink);font-size:12px;padding-left:0;text-overflow:ellipsis}.scope-line{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:37px;font-size:11px;color:var(--muted)}.scope-line p{margin:5px 0;overflow-wrap:anywhere}.scope-line button{white-space:nowrap}.status-dot{width:6px;height:6px;border-radius:50%;background:var(--teal)}.has-warning .status-dot{background:var(--amber)}.has-warning{color:#96621c}.breadcrumbs{display:flex;flex-wrap:wrap;gap:6px}.breadcrumbs:not(:empty){margin-bottom:10px}.breadcrumbs button{display:inline-flex;align-items:center;font-size:11px;min-height:26px;background:#e0ebfa;border-color:#cadaef;color:#285994;max-width:min(100%,360px);text-align:left}.chip-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.breadcrumbs svg{display:inline-block;vertical-align:middle;width:12px;height:12px;margin-left:6px}
|
|
4
|
+
.number,table,.six-stats strong{font-family:"SFMono-Regular",Consolas,"Liberation Mono",monospace;font-variant-numeric:tabular-nums}.metrics{display:grid;grid-template-columns:minmax(165px,1.25fr) minmax(0,4fr) minmax(170px,1.15fr);background:white;border:1px solid var(--line);border-radius:7px;margin-bottom:14px;overflow:hidden}.metrics>div{padding:17px 20px;min-width:0}.total-metric{position:relative;border-right:1px solid var(--line)}.eyebrow{display:block;font-size:10px;color:var(--muted);letter-spacing:.6px;font-weight:500}.total-metric>strong{display:block;font-size:31px;font-weight:550;letter-spacing:-1.3px;line-height:1.5}.metric-note{font-size:10px;color:var(--muted);display:block;line-height:1.5}.token-ribbon{display:flex;position:absolute;bottom:0;left:0;right:0;height:3px}.token-ribbon span{display:block}.metrics .composition{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));padding:0}.token-part{display:flex;flex-direction:column;justify-content:center;gap:7px;padding:15px 17px;min-width:0}.token-part>span{font-size:11px;color:var(--muted);white-space:nowrap}.token-part i,.legend i{display:inline-block;width:7px;height:7px;border-radius:2px;margin-right:6px}.token-part strong{font-size:22px;letter-spacing:-.8px;font-weight:500}.token-part small{font-size:10px;color:var(--muted)}.cost-metric{border-left:1px solid var(--line)}.cost-metric>strong{display:block;font-size:21px;font-weight:500;line-height:1.8;letter-spacing:-.6px;white-space:nowrap}.coverage-track{height:3px;background:#e6ebf1;overflow:hidden;border-radius:2px;margin:4px 0 6px}.coverage-track span{display:block;height:100%;background:#849bb5}
|
|
5
|
+
.dashboard-grid{display:grid;grid-template-columns:1fr 1.28fr 1fr;gap:14px}.panel{background:white;border:1px solid var(--line);border-radius:7px;min-width:0;padding:16px 18px;overflow:hidden}.panel-head{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:28px;margin-bottom:12px}.panel-head h1,.panel-head h2{display:flex;gap:8px;align-items:center;font-size:13px;font-weight:600;white-space:nowrap;margin:0}.panel-head h1 svg,.panel-head h2 svg{color:#7c8fa8}.panel-head select{font-size:11px;background:var(--soft);border-color:transparent;max-width:140px;min-height:28px;padding:4px 6px}.badge{display:inline-flex;font-size:10px;white-space:nowrap;color:var(--muted);background:var(--soft);padding:4px 6px;border-radius:4px}.muted{color:var(--muted);font-size:11px}.trend-panel{grid-column:span 2}.trend-panel,.request-panel{height:286px}.chart{width:100%;height:auto}.chart text{font:10px "SFMono-Regular",Consolas,monospace;fill:var(--muted)}#trend .chart{height:200px}.gridline{stroke:#e8edf3;stroke-dasharray:3 4}.chart [role=button]{cursor:pointer}.chart [role=button]:hover .hit,.chart [role=button]:focus-visible .hit{fill:#3978ca16;stroke:#3978ca;stroke-width:.7}.chart [role=button]:focus-visible{outline:none}.chart-footer{display:flex;gap:8px;align-items:center;justify-content:space-between;font-size:10px}.legend{display:flex;gap:12px;flex-wrap:wrap;color:var(--muted);font-size:10px}.legend span{white-space:nowrap}.chart-footer>.muted{font-size:10px}.request-visual{display:flex;align-items:center;gap:15px;height:186px}#request-ring{width:120px;flex-shrink:0}#request-ring svg{width:100%}.ring-center{font:600 25px "SFMono-Regular",Consolas,monospace;fill:var(--ink);letter-spacing:-1px}.ring-caption{font:10px sans-serif;fill:var(--muted)}#request-summary{flex:1;min-width:0;display:grid;gap:9px}#request-summary button{display:grid;grid-template-columns:1fr auto;gap:6px;align-items:center;width:100%;background:transparent;border:1px solid transparent;min-height:37px;padding:4px;text-align:left;font-size:10px}#request-summary button:hover,#request-summary button[aria-pressed=true]{background:#f0f5fc;border-color:#c3d5ed}#request-summary .request-label{display:flex;gap:5px;align-items:center;white-space:nowrap}#request-summary i{width:6px;height:6px;border-radius:50%;flex-shrink:0}#request-summary strong{font:12px "SFMono-Regular",Consolas,monospace}.request-track{grid-column:1/-1;height:3px;background:var(--soft)}.request-track span{display:block;height:100%}.panel-foot{border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:6px;padding-top:10px;margin-top:12px;font-size:10px;color:var(--muted);min-height:25px}.harness-panel,.ranking-panel,.daily-panel{height:316px;display:flex;flex-direction:column}.ranking{flex:1;overflow:auto;min-height:0;scrollbar-width:thin}.rank-row{display:block;width:100%;border:0;border-radius:3px;background:transparent;padding:7px 0;text-align:left;min-height:34px}.rank-row:hover{background:#f2f6fc}.rank-labels{display:flex;align-items:center;gap:8px;font-size:11px;margin-bottom:6px}.rank-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rank-value{font-size:11px;white-space:nowrap}.rank-share{width:43px;text-align:right;color:var(--muted);font-size:10px;white-space:nowrap}.rank-track{display:flex;height:4px;background:#eff3f8;border-radius:2px;overflow:hidden}.rank-track span{height:100%;display:block}.harness-panel .rank-row{padding:10px 0}.daily-hero{display:flex;align-items:center;justify-content:space-between;gap:6px}.daily-hero strong{display:block;font-size:26px;font-weight:500;letter-spacing:-1px;margin:5px 0}.six-stats{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px}.six-stats>div{min-width:0}.six-stats span{display:block;color:var(--muted);font-size:9px;margin-bottom:6px}.six-stats strong{font-size:10px;font-weight:500;white-space:nowrap}.six-stats .median{color:#2969b6}.daily-panel .six-stats{margin-top:2px}.daily-panel .panel-foot{margin-top:auto}#daily-histogram .chart{height:68px}.range-chart{width:100%;height:36px;min-width:120px}.range-empty{height:36px;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:11px}.range-chart .range-line{stroke:#9fb6d2;stroke-width:2}.range-chart .range-box{fill:#d4e4f8}.range-chart path,.range-chart circle{vector-effect:non-scaling-stroke}.range-chart .range-median{fill:#3978ca;stroke:#3978ca;stroke-width:7}.range-chart .range-p90{fill:white;stroke:#3978ca;stroke-width:1.4}.view-heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:22px 0 14px}.view-heading h1{font:600 22px "Avenir Next",Avenir,sans-serif;letter-spacing:-.5px;margin:0}.view-heading select{font-size:12px}.sample-controls,.simulation-caption{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 0 18px;color:var(--muted);font-size:12px}.checkbox{display:flex;align-items:center;gap:6px;white-space:nowrap}.checkbox input{accent-color:var(--blue);min-height:auto}.analysis-grid{display:grid;grid-template-columns:1.35fr 1fr;gap:14px;margin-bottom:14px}.quantile-row{padding:13px 0;border-bottom:1px solid var(--line)}.quantile-title{display:flex;justify-content:space-between;font-size:12px;margin-bottom:3px}.quantile-title span:last-child{font-size:10px;color:var(--muted)}.quantile-row:last-child{border:0}.quantile-row .six-stats strong{font-size:11px}.notice{background:#fff7e9;border:1px solid #f0dfbf;color:#855820;border-radius:5px;padding:12px;font-size:12px;line-height:1.7;overflow-wrap:anywhere}#cost-range{margin-top:28px}#cost-stats{margin:15px 0 28px}.range-legend{display:flex;gap:16px;color:var(--muted);font-size:10px;flex-wrap:wrap}.simulation-panel .table-wrap{max-height:none}.simulation-panel td:first-child{max-width:290px}.simulation-panel td:nth-child(2){min-width:200px;width:24%}.simulation-panel .range-chart{height:28px}.simulation-panel th:nth-child(6),.simulation-panel td:nth-child(6){background:#f0f5fc;color:#2969b6}.simulation-panel .unavailable{color:var(--muted);font-family:inherit;font-size:11px}.table-wrap{overflow:auto;max-height:560px;scrollbar-width:thin}table{border-collapse:separate;border-spacing:0;width:100%;font-size:11px;text-align:right;white-space:nowrap}th,td{padding:12px 10px;border-bottom:1px solid var(--line)}th{position:sticky;top:0;background:var(--soft);font:500 10px -apple-system,BlinkMacSystemFont,sans-serif;color:var(--muted);z-index:2}th:first-child,td:first-child{text-align:left;position:sticky;left:0;background:white;min-width:110px;max-width:220px;white-space:normal;overflow-wrap:anywhere}td:first-child{z-index:1}th:first-child{z-index:3;background:var(--soft)}tbody tr:hover td{background:#f3f7fc}td button{font-size:11px;padding:3px 6px;min-height:26px}.pagination{display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-top:12px;font-size:11px;color:var(--muted)}.pagination>span{margin-right:8px}details{border-top:1px solid var(--line);margin-top:14px;padding-top:12px}summary{font-size:12px;color:var(--muted);padding:4px 0;line-height:1.7}details[open]>summary{margin-bottom:12px}.empty{color:var(--muted);font-size:12px;text-align:center;padding:32px 15px}#empty-state{padding:12px;margin-bottom:14px;border:1px dashed #a8bdd6;border-radius:5px}footer{display:flex;align-items:center;justify-content:space-between;gap:10px;color:#748399;font-size:10px;padding:16px 0}footer>span{display:flex;align-items:center;gap:6px}footer svg.icon{width:12px;height:12px}dialog{width:min(850px,calc(100% - 32px));max-height:85vh;border:1px solid var(--line);padding:0;border-radius:10px;color:var(--ink);box-shadow:0 24px 100px #10254140}dialog::backdrop{background:#13243d66;backdrop-filter:blur(3px)}.dialog-head{display:flex;justify-content:space-between;align-items:center;padding:18px 24px;border-bottom:1px solid var(--line);position:sticky;top:0;background:white;z-index:5}.dialog-head h2{font-size:17px;margin:0}.dialog-body{padding:6px 24px 24px}.dialog-body p{line-height:1.9;font-size:12px;color:var(--muted)}.dialog-body .notice{color:#855820}.dialog-body table{font-size:10px}noscript{padding:30px;display:block}
|
|
6
|
+
@media(min-width:1600px){.request-visual{gap:24px}#request-ring{width:145px}.trend-panel,.request-panel{height:306px}#trend .chart{height:220px}.request-visual{height:206px}}
|
|
7
|
+
@media(max-width:1150px){.app-header{gap:20px}.views button{padding-inline:12px}.metrics{grid-template-columns:1.1fr 3fr 1.15fr}.metrics>div{padding:15px}.token-part{padding:12px 10px}.token-part strong{font-size:19px}.token-part>span{font-size:10px}.dashboard-grid{grid-template-columns:1fr 1fr}.trend-panel{grid-column:span 2}.request-panel{grid-column:2;grid-row:2}.harness-panel{grid-column:1;grid-row:2;height:286px}.ranking-panel,.daily-panel{height:316px}.request-visual{justify-content:center}#request-ring{width:135px}#request-summary{max-width:220px}.analysis-grid{grid-template-columns:1fr}.scope-line p{font-size:10px}}
|
|
8
|
+
@media(max-width:760px){.app-header{height:auto;min-height:64px;padding:12px 16px 0;flex-wrap:wrap;gap:10px}.brand{flex:1}.app-header>.actions{order:1}.views{order:2;width:100%;flex:auto;height:43px;gap:0}.views button{flex:1;justify-content:center;padding:0 6px;gap:5px;font-size:11px}main{padding:14px 14px 0}.date-filter{flex:1;justify-content:space-between;gap:3px;padding:0 5px}.date-filter input{width:122px;font-size:11px}.date-filter svg{width:14px}.filter-spacer{display:none}.filters>.icon-button{order:0}.presets{order:1;width:100%}.dimension-filters{order:2;grid-template-columns:1fr 1fr;gap:7px}.dimension-filters label:last-child{grid-column:1/-1}.dimension-filters label{padding-left:7px;gap:5px}.dimension-filters select{font-size:11px}.scope-line{align-items:flex-start;padding-block:5px}.scope-line p{max-width:230px;line-height:1.6}.scope-line button{font-size:10px}.metrics{grid-template-columns:1fr 1fr;margin-bottom:12px}.metrics .composition{grid-column:1/-1;grid-row:2;border-top:1px solid var(--line)}.metrics>div{padding:13px 15px}.total-metric{border-right:0}.cost-metric{border-left:1px solid var(--line)}.token-part{padding:13px 8px}.token-part>span{font-size:9px;letter-spacing:-.3px}.token-part i{width:5px;height:5px;margin-right:4px}.token-part strong{font-size:18px}.total-metric>strong{font-size:28px}.cost-metric>strong{font-size:20px}.dashboard-grid{grid-template-columns:minmax(0,1fr);gap:12px}.dashboard-grid>.panel{grid-column:auto;grid-row:auto}.panel{padding:14px}.trend-panel{height:280px}.request-panel{height:270px}.request-visual{height:171px;gap:20px}.harness-panel{height:300px}.ranking-panel,.daily-panel{height:320px}.panel-head{gap:5px}.panel-head .actions{gap:4px}.panel-head h1,.panel-head h2{font-size:12px}.panel-head select{max-width:108px;font-size:10px}.trend-panel .badge{display:none}.chart-footer{align-items:flex-start}.chart-footer>.muted{display:none}.legend{gap:8px;font-size:9px}.six-stats strong{font-size:10px}.sample-controls,.simulation-caption{align-items:flex-start;flex-direction:column;gap:10px}.view-heading{flex-wrap:wrap}.view-heading h1{font-size:20px}.analysis-grid{gap:12px}.range-legend{gap:8px}.simulation-panel .panel-head{align-items:flex-start;flex-direction:column}.table-wrap{max-height:480px}th:first-child,td:first-child{min-width:96px;max-width:140px}.simulation-panel td:first-child{max-width:140px}.quantile-row .six-stats strong{font-size:10px}footer{align-items:flex-start;flex-direction:column;font-size:9px;gap:8px}.dialog-head{padding:14px 16px}.dialog-body{padding:6px 16px 18px}#daily-histogram .chart{height:80px}.pagination{gap:6px}}
|
|
9
|
+
@media(prefers-reduced-motion:no-preference){button{transition:background .12s,border-color .12s}}
|
|
10
|
+
@media print{body{background:white;print-color-adjust:exact;-webkit-print-color-adjust:exact}.app-header,.filters,.scope-line button,.breadcrumbs,.icon-button,.presets,.pagination{display:none}main{padding:0;max-width:none}.metrics{margin-top:0}.panel{break-inside:avoid}.table-wrap{max-height:none!important;overflow:visible}.dashboard-grid{gap:10px}footer{margin-top:10px}}
|