takomi 2.1.21 → 2.1.24
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.
|
@@ -44,7 +44,11 @@ const SUBCOMMAND_COMPLETIONS: Record<string, TakomiCompletion[]> = {
|
|
|
44
44
|
{ value: "daily", label: "daily", description: "Show daily usage rows" },
|
|
45
45
|
{ value: "models", label: "models", description: "Show model usage leaderboard" },
|
|
46
46
|
{ value: "projects", label: "projects", description: "Show project usage leaderboard" },
|
|
47
|
-
{ value: "
|
|
47
|
+
{ value: "projects-full", label: "projects-full", description: "Show full project names" },
|
|
48
|
+
{ value: "sessions", label: "sessions", description: "Show longest active main sessions" },
|
|
49
|
+
{ value: "sessions-full", label: "sessions-full", description: "Show full session names and files" },
|
|
50
|
+
{ value: "tasks", label: "tasks", description: "Show longest tasks by active turn span" },
|
|
51
|
+
{ value: "tasks-full", label: "tasks-full", description: "Show full task prompts" },
|
|
48
52
|
{ value: "tools", label: "tools", description: "Show most used tools" },
|
|
49
53
|
{ value: "agents", label: "agents", description: "Show main agent role leaderboard" },
|
|
50
54
|
{ value: "subagents", label: "subagents", description: "Show subagent run leaderboard" },
|
|
@@ -95,7 +99,7 @@ export function commandHelp(): string {
|
|
|
95
99
|
"/takomi mode <direct|orchestrate|review>",
|
|
96
100
|
"/takomi gate <auto|review>",
|
|
97
101
|
"/takomi subagents <on|off|status|expand|collapse|toggle>",
|
|
98
|
-
"/takomi stats [overview|daily|models|projects|sessions|tools|subagents|sources] [since 7d]",
|
|
102
|
+
"/takomi stats [overview|daily|models|projects|projects-full|sessions|sessions-full|tasks|tasks-full|tools|subagents|sources] [since 7d]",
|
|
99
103
|
"/takomi routing [show|where]",
|
|
100
104
|
"/takomi routing <policy text> # updates global policy",
|
|
101
105
|
"/takomi routing local <policy text> # project override",
|
|
@@ -306,8 +306,8 @@ function center(text, width) {
|
|
|
306
306
|
return ' '.repeat(pad) + text;
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
function statCard(value, label) {
|
|
310
|
-
return { value: String(value), label };
|
|
309
|
+
function statCard(value, label, color = pc.white) {
|
|
310
|
+
return { value: String(value), label, color };
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
// ── Table Helper ────────────────────────────────────────────────────────────
|
|
@@ -348,11 +348,42 @@ function runLabel(run, width = 28) {
|
|
|
348
348
|
const label = run ? `${run.agent || 'unknown'}: ${run.task || ''}`.trim() : '-';
|
|
349
349
|
return label.length > width ? label.slice(0, width - 1) + '…' : label;
|
|
350
350
|
}
|
|
351
|
+
function indentWrap(text, width = 76, indent = ' ') {
|
|
352
|
+
const words = String(text || '').split(/\s+/).filter(Boolean);
|
|
353
|
+
const lines = [];
|
|
354
|
+
let line = '';
|
|
355
|
+
for (const word of words) {
|
|
356
|
+
if ((line + ' ' + word).trim().length > width && line) { lines.push(line); line = word; }
|
|
357
|
+
else line = (line + ' ' + word).trim();
|
|
358
|
+
}
|
|
359
|
+
if (line) lines.push(line);
|
|
360
|
+
return lines.map((l, i) => (i ? indent : '') + l).join('\n');
|
|
361
|
+
}
|
|
362
|
+
function renderFullList(title, rows, renderRow, limit = 20) {
|
|
363
|
+
const lines = ['\n' + pc.bold(pc.magenta('Takomi Stats')), renderTable(title, [], [{ width: 74 }])];
|
|
364
|
+
rows.slice(0, limit).forEach((row, i) => lines.push(renderRow(row, i)));
|
|
365
|
+
lines.push('\n' + pc.dim('Privacy: metadata only · no raw prompts or transcripts'));
|
|
366
|
+
return lines.join('\n');
|
|
367
|
+
}
|
|
351
368
|
|
|
352
369
|
function renderFocusedView(stats, opts = {}) {
|
|
353
370
|
const view = opts.view;
|
|
354
371
|
const limit = opts.limit || 20;
|
|
355
372
|
if (!view || view === 'overview') return null;
|
|
373
|
+
if (view === 'projects-full' || view === 'project-full') return renderFullList('Top Projects — Full Names', stats.byProject, (r, i) => [
|
|
374
|
+
` ${pc.dim(String(i + 1).padStart(2, '0') + '.')} ${pc.white(r.key)}`,
|
|
375
|
+
` ${pc.cyan(fmtTokens(r.total))} ${pc.dim(fmtMoney(r.cost))} ${pc.dim(r.events + ' calls')}`,
|
|
376
|
+
].join('\n'), limit);
|
|
377
|
+
if (view === 'sessions-full' || view === 'session-full') return renderFullList('Longest Active Sessions — Full Names', stats.topSessions, (r, i) => [
|
|
378
|
+
` ${pc.dim(String(i + 1).padStart(2, '0') + '.')} ${pc.white(r.project || r.cwd || r.key || 'unknown')}`,
|
|
379
|
+
` ${pc.cyan(ms(sessionDuration(r)))} ${pc.dim(r.turns + ' turns')} ${pc.dim(r.toolCalls + ' tools')}`,
|
|
380
|
+
` ${pc.dim(r.file || '')}`,
|
|
381
|
+
].join('\n'), limit);
|
|
382
|
+
if (view === 'tasks-full' || view === 'task-full') return renderFullList('Longest Tasks — Full Prompts', stats.topTasks || [], (r, i) => [
|
|
383
|
+
` ${pc.dim(String(i + 1).padStart(2, '0') + '.')} ${pc.cyan(ms(taskDuration(r)))} ${pc.magenta(r.toolCalls + ' tools')} ${pc.dim(dayOf(r.start))}`,
|
|
384
|
+
` ${pc.white(indentWrap(r.title || r.project || r.session || 'unknown'))}`,
|
|
385
|
+
` ${pc.dim(r.project || '')}`,
|
|
386
|
+
].join('\n'), limit);
|
|
356
387
|
const tables = {
|
|
357
388
|
models: ['Top Models', stats.byModel, [
|
|
358
389
|
{ width: 26, align: 'left', get: r => pc.white(r.key) },
|
|
@@ -398,7 +429,7 @@ function renderFocusedView(stats, opts = {}) {
|
|
|
398
429
|
tasks: ['Longest Tasks', stats.topTasks || [], [
|
|
399
430
|
{ width: 6, align: 'left', get: r => pc.dim(dayOf(r.start).slice(5)) },
|
|
400
431
|
{ width: 9, align: 'right', get: r => pc.cyan(ms(taskDuration(r))) },
|
|
401
|
-
{ width:
|
|
432
|
+
{ width: 11, align: 'right', get: r => pc.magenta(`${r.toolCalls} tools`) },
|
|
402
433
|
{ width: 36, align: 'left', get: r => pc.white(taskLabel(r, 36)) },
|
|
403
434
|
]],
|
|
404
435
|
daily: ['Daily Usage', [...stats.byDay].reverse(), [
|
|
@@ -455,8 +486,9 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
455
486
|
for (const c of cards) {
|
|
456
487
|
const vPad = Math.max(0, Math.floor((cardW - c.value.length) / 2));
|
|
457
488
|
const lPad = Math.max(0, Math.floor((cardW - c.label.length) / 2));
|
|
458
|
-
const
|
|
459
|
-
const
|
|
489
|
+
const color = c.color || pc.white;
|
|
490
|
+
const vContent = ' '.repeat(vPad) + pc.bold(color(c.value));
|
|
491
|
+
const lContent = ' '.repeat(lPad) + pc.dim(color(c.label));
|
|
460
492
|
// Pad to cardW visible chars
|
|
461
493
|
vStr += ansiPadEnd(vContent, cardW);
|
|
462
494
|
lStr += ansiPadEnd(lContent, cardW);
|
|
@@ -486,11 +518,11 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
486
518
|
// ── Duration Cards ────────────────────────────────────────────────────
|
|
487
519
|
lines.push('');
|
|
488
520
|
const cards3 = [
|
|
489
|
-
statCard(longestSession ? ms(sessionDuration(longestSession)) : '-', '
|
|
490
|
-
statCard(longestSession ? String(longestSession.turns) : '-', 'Session
|
|
491
|
-
statCard(longestTask ? ms(taskDuration(longestTask)) : '-', 'Longest Task'),
|
|
492
|
-
statCard(longestTask ? String(longestTask.toolCalls) : '-', 'Task
|
|
493
|
-
statCard(stats.mostSubagentsSession ? String(stats.mostSubagentsSession.subagentCalls) : '0', '
|
|
521
|
+
statCard(longestSession ? ms(sessionDuration(longestSession)) : '-', 'Active Session', pc.cyan),
|
|
522
|
+
statCard(longestSession ? String(longestSession.turns) : '-', 'Turns in Session', pc.cyan),
|
|
523
|
+
statCard(longestTask ? ms(taskDuration(longestTask)) : '-', 'Longest Task', pc.magenta),
|
|
524
|
+
statCard(longestTask ? String(longestTask.toolCalls) : '-', 'Tools in Task', pc.magenta),
|
|
525
|
+
statCard(stats.mostSubagentsSession ? String(stats.mostSubagentsSession.subagentCalls) : '0', 'Max Subagents', pc.blue),
|
|
494
526
|
];
|
|
495
527
|
const [v3, l3] = buildCardLines(cards3);
|
|
496
528
|
lines.push(v3);
|
|
@@ -560,7 +592,7 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
560
592
|
lines.push(renderTable('Longest Tasks', stats.topTasks.slice(0, 5), [
|
|
561
593
|
{ width: 6, align: 'left', get: r => pc.dim(dayOf(r.start).slice(5)) },
|
|
562
594
|
{ width: 9, align: 'right', get: r => pc.cyan(ms(taskDuration(r))) },
|
|
563
|
-
{ width:
|
|
595
|
+
{ width: 11, align: 'right', get: r => pc.magenta(`${r.toolCalls} tools`) },
|
|
564
596
|
{ width: 34, align: 'left', get: r => pc.white(taskLabel(r, 34)) },
|
|
565
597
|
]));
|
|
566
598
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "takomi",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.24",
|
|
4
4
|
"description": "🎯 Stop wrestling with AI. Start building with purpose. The artisan's toolkit for agent workflows, Codex skills, and original Takomi capabilities like 21st.dev integration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/takomi-stats.js
CHANGED
|
@@ -88,10 +88,10 @@ async function files(root, suffix = '.jsonl') {
|
|
|
88
88
|
await walk(root); return out;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
async function scanPiSessions(root, source, events, sessionRows = []) {
|
|
91
|
+
async function scanPiSessions(root, source, events, sessionRows = [], taskRows = []) {
|
|
92
92
|
for (const file of await files(root)) {
|
|
93
|
-
let provider = 'unknown', model = 'unknown', session = path.basename(file, '.jsonl'), cwd = '';
|
|
94
|
-
const row = { key: session, session, source, file, project: projectKey(file), cwd, start: '', end: '', turns: 0, messages: 0, toolCalls: 0, subagentCalls: 0, roles: new Map(), stages: new Map(), workflows: new Map() };
|
|
93
|
+
let provider = 'unknown', model = 'unknown', session = path.basename(file, '.jsonl'), cwd = '', currentTask = null;
|
|
94
|
+
const row = { key: session, session, source, file, project: projectKey(file), cwd, start: '', end: '', turns: 0, messages: 0, toolCalls: 0, subagentCalls: 0, roles: new Map(), stages: new Map(), workflows: new Map(), activeMs: 0 };
|
|
95
95
|
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
96
96
|
for (const line of text.split(/\r?\n/)) {
|
|
97
97
|
const obj = safeJson(line); if (!obj) continue;
|
|
@@ -110,11 +110,20 @@ async function scanPiSessions(root, source, events, sessionRows = []) {
|
|
|
110
110
|
const msg = obj.type === 'message' && obj.message ? obj.message : null;
|
|
111
111
|
if (msg) {
|
|
112
112
|
row.messages += 1;
|
|
113
|
-
|
|
113
|
+
const ts = obj.timestamp || msg.timestamp || '';
|
|
114
|
+
if (msg.role === 'user') {
|
|
115
|
+
if (currentTask?.end && currentTask.end !== currentTask.start) taskRows.push(currentTask);
|
|
116
|
+
row.turns += 1;
|
|
117
|
+
const textPart = (msg.content || []).find(p => p?.type === 'text')?.text || '';
|
|
118
|
+
currentTask = { source, file, session, project: projectKey(file), cwd, start: ts, end: ts, provider, model, turns: 1, toolCalls: 0, title: String(textPart).replace(/\s+/g, ' ').trim() };
|
|
119
|
+
} else if (currentTask && ts) {
|
|
120
|
+
currentTask.end = ts;
|
|
121
|
+
}
|
|
114
122
|
for (const part of msg.content || []) {
|
|
115
123
|
if (!part || part.type !== 'toolCall') continue;
|
|
116
124
|
const name = part.name || 'unknown';
|
|
117
125
|
row.toolCalls += 1;
|
|
126
|
+
if (currentTask) currentTask.toolCalls += 1;
|
|
118
127
|
if (name === 'takomi_subagent') {
|
|
119
128
|
const args = part.arguments || {};
|
|
120
129
|
const count = Array.isArray(args.tasks) ? args.tasks.length : Array.isArray(args.chain) ? args.chain.length : 1;
|
|
@@ -126,6 +135,8 @@ async function scanPiSessions(root, source, events, sessionRows = []) {
|
|
|
126
135
|
const u = msg && msg.usage;
|
|
127
136
|
if (u) events.push({ source, file, timestamp: obj.timestamp, day: dayOf(obj.timestamp), session, provider, model, project: projectKey(file), kind: 'usage', input: +u.input||0, cache: +u.cacheRead||0, output: +u.output||0, total: +u.totalTokens||0, cost: cost(model, +u.input||0, +u.cacheRead||0, +u.output||0, true) });
|
|
128
137
|
}
|
|
138
|
+
if (currentTask?.end && currentTask.end !== currentTask.start) taskRows.push(currentTask);
|
|
139
|
+
row.activeMs = taskRows.filter(t => t.file === file).reduce((sum, t) => sum + taskDuration(t), 0);
|
|
129
140
|
if (row.messages || row.toolCalls || row.turns) sessionRows.push(row);
|
|
130
141
|
}
|
|
131
142
|
}
|
|
@@ -141,13 +152,16 @@ async function scanRunHistory(file) {
|
|
|
141
152
|
export async function collectTakomiStats(opts = {}) {
|
|
142
153
|
const home = opts.home || os.homedir();
|
|
143
154
|
const cwd = opts.cwd || process.cwd();
|
|
144
|
-
const rawEvents = [], rawSessions = [];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
await scanPiSessions(
|
|
155
|
+
const rawEvents = [], rawSessions = [], rawTasks = [];
|
|
156
|
+
const globalSessions = path.resolve(path.join(home, '.pi', 'agent', 'sessions'));
|
|
157
|
+
const projectSessions = path.resolve(path.join(cwd, '.pi', 'agent', 'sessions'));
|
|
158
|
+
await scanPiSessions(globalSessions, 'pi-global', rawEvents, rawSessions, rawTasks);
|
|
159
|
+
if (projectSessions !== globalSessions) await scanPiSessions(projectSessions, 'pi-project', rawEvents, rawSessions, rawTasks);
|
|
160
|
+
await scanPiSessions(path.join(cwd, '.pi', 'takomi'), 'takomi-project', rawEvents, rawSessions, rawTasks);
|
|
148
161
|
const sinceDay = parseSince(opts.since);
|
|
149
162
|
const events = rawEvents.filter(e => !sinceDay || e.day >= sinceDay);
|
|
150
163
|
const sessionRows = rawSessions.filter(s => !sinceDay || dayOf(s.end || s.start) >= sinceDay);
|
|
164
|
+
const taskRows = rawTasks.filter(t => !sinceDay || dayOf(t.end || t.start) >= sinceDay);
|
|
151
165
|
const runs = await scanRunHistory(path.join(home, '.pi', 'agent', 'run-history.jsonl'));
|
|
152
166
|
const byDay = new Map(), byModel = new Map(), bySource = new Map(), byProject = new Map(), byTool = new Map(), byRole = new Map(), byStage = new Map(), byWorkflow = new Map();
|
|
153
167
|
let totals = { input: 0, cache: 0, output: 0, total: 0, cost: 0, events: events.filter(e => e.kind === 'usage').length, toolCalls: 0, turns: 0 };
|
|
@@ -165,9 +179,10 @@ export async function collectTakomiStats(opts = {}) {
|
|
|
165
179
|
}
|
|
166
180
|
const byAgent = new Map(); let longestRun = null;
|
|
167
181
|
for (const r of runs) { add(byAgent, r.agent || 'unknown', { total: 0, events: 1 }); if (!longestRun || (+r.duration||0) > (+longestRun.duration||0)) longestRun = r; }
|
|
168
|
-
const topSessions = [...sessionRows].sort((a,b)=>b.turns-a.turns || b.toolCalls-a.toolCalls).slice(0, 20);
|
|
182
|
+
const topSessions = [...sessionRows].sort((a,b)=>(b.activeMs||0)-(a.activeMs||0) || b.turns-a.turns || b.toolCalls-a.toolCalls).slice(0, 20);
|
|
183
|
+
const topTasks = [...taskRows].sort((a,b)=>taskDuration(b)-taskDuration(a) || b.toolCalls-a.toolCalls).slice(0, 20);
|
|
169
184
|
const mostSubagentsSession = [...sessionRows].sort((a,b)=>b.subagentCalls-a.subagentCalls)[0] || null;
|
|
170
|
-
return { generatedAt: new Date().toISOString(), cwd, since: sinceDay, totals, sessions: new Set([...events.map(e => e.session), ...sessionRows.map(s => s.session)]).size, byDay: [...byDay.values()].sort((a,b)=>a.key.localeCompare(b.key)), byModel: [...byModel.values()].sort((a,b)=>b.total-a.total), bySource: [...bySource.values()].sort((a,b)=>b.total-a.total), byProject: [...byProject.values()].sort((a,b)=>b.total-a.total), byTool: [...byTool.values()].sort((a,b)=>b.events-a.events), byRole: [...byRole.values()].sort((a,b)=>b.events-a.events), byStage: [...byStage.values()].sort((a,b)=>b.events-a.events), byWorkflow: [...byWorkflow.values()].sort((a,b)=>b.events-a.events), byAgent: [...byAgent.values()].sort((a,b)=>b.events-a.events), sessionRows, topSessions, mostSubagentsSession, runs, longestRun, recent: events.sort((a,b)=>(b.timestamp||'').localeCompare(a.timestamp||'')).slice(0, 10) };
|
|
185
|
+
return { generatedAt: new Date().toISOString(), cwd, since: sinceDay, totals, sessions: new Set([...events.map(e => e.session), ...sessionRows.map(s => s.session)]).size, byDay: [...byDay.values()].sort((a,b)=>a.key.localeCompare(b.key)), byModel: [...byModel.values()].sort((a,b)=>b.total-a.total), bySource: [...bySource.values()].sort((a,b)=>b.total-a.total), byProject: [...byProject.values()].sort((a,b)=>b.total-a.total), byTool: [...byTool.values()].sort((a,b)=>b.events-a.events), byRole: [...byRole.values()].sort((a,b)=>b.events-a.events), byStage: [...byStage.values()].sort((a,b)=>b.events-a.events), byWorkflow: [...byWorkflow.values()].sort((a,b)=>b.events-a.events), byAgent: [...byAgent.values()].sort((a,b)=>b.events-a.events), sessionRows, taskRows, topSessions, topTasks, mostSubagentsSession, runs, longestRun, recent: events.sort((a,b)=>(b.timestamp||'').localeCompare(a.timestamp||'')).slice(0, 10) };
|
|
171
186
|
}
|
|
172
187
|
|
|
173
188
|
// ── Streak Calculation ──────────────────────────────────────────────────────
|
|
@@ -291,8 +306,8 @@ function center(text, width) {
|
|
|
291
306
|
return ' '.repeat(pad) + text;
|
|
292
307
|
}
|
|
293
308
|
|
|
294
|
-
function statCard(value, label) {
|
|
295
|
-
return { value: String(value), label };
|
|
309
|
+
function statCard(value, label, color = pc.white) {
|
|
310
|
+
return { value: String(value), label, color };
|
|
296
311
|
}
|
|
297
312
|
|
|
298
313
|
// ── Table Helper ────────────────────────────────────────────────────────────
|
|
@@ -317,11 +332,58 @@ function sessionLabel(row, width = 36) {
|
|
|
317
332
|
return project.length > width ? '…' + project.slice(-(width - 1)) : project;
|
|
318
333
|
}
|
|
319
334
|
function sessionDay(row) { return dayOf(row.start || row.end).slice(5) || '??-??'; }
|
|
335
|
+
function sessionDuration(row) {
|
|
336
|
+
return row?.activeMs || 0;
|
|
337
|
+
}
|
|
338
|
+
function taskDuration(row) {
|
|
339
|
+
const start = Date.parse(row?.start || '');
|
|
340
|
+
const end = Date.parse(row?.end || '');
|
|
341
|
+
return Number.isFinite(start) && Number.isFinite(end) && end >= start ? end - start : 0;
|
|
342
|
+
}
|
|
343
|
+
function taskLabel(row, width = 34) {
|
|
344
|
+
const label = row?.title || row?.project || row?.session || 'unknown';
|
|
345
|
+
return label.length > width ? label.slice(0, width - 1) + '…' : label;
|
|
346
|
+
}
|
|
347
|
+
function runLabel(run, width = 28) {
|
|
348
|
+
const label = run ? `${run.agent || 'unknown'}: ${run.task || ''}`.trim() : '-';
|
|
349
|
+
return label.length > width ? label.slice(0, width - 1) + '…' : label;
|
|
350
|
+
}
|
|
351
|
+
function indentWrap(text, width = 76, indent = ' ') {
|
|
352
|
+
const words = String(text || '').split(/\s+/).filter(Boolean);
|
|
353
|
+
const lines = [];
|
|
354
|
+
let line = '';
|
|
355
|
+
for (const word of words) {
|
|
356
|
+
if ((line + ' ' + word).trim().length > width && line) { lines.push(line); line = word; }
|
|
357
|
+
else line = (line + ' ' + word).trim();
|
|
358
|
+
}
|
|
359
|
+
if (line) lines.push(line);
|
|
360
|
+
return lines.map((l, i) => (i ? indent : '') + l).join('\n');
|
|
361
|
+
}
|
|
362
|
+
function renderFullList(title, rows, renderRow, limit = 20) {
|
|
363
|
+
const lines = ['\n' + pc.bold(pc.magenta('Takomi Stats')), renderTable(title, [], [{ width: 74 }])];
|
|
364
|
+
rows.slice(0, limit).forEach((row, i) => lines.push(renderRow(row, i)));
|
|
365
|
+
lines.push('\n' + pc.dim('Privacy: metadata only · no raw prompts or transcripts'));
|
|
366
|
+
return lines.join('\n');
|
|
367
|
+
}
|
|
320
368
|
|
|
321
369
|
function renderFocusedView(stats, opts = {}) {
|
|
322
370
|
const view = opts.view;
|
|
323
371
|
const limit = opts.limit || 20;
|
|
324
372
|
if (!view || view === 'overview') return null;
|
|
373
|
+
if (view === 'projects-full' || view === 'project-full') return renderFullList('Top Projects — Full Names', stats.byProject, (r, i) => [
|
|
374
|
+
` ${pc.dim(String(i + 1).padStart(2, '0') + '.')} ${pc.white(r.key)}`,
|
|
375
|
+
` ${pc.cyan(fmtTokens(r.total))} ${pc.dim(fmtMoney(r.cost))} ${pc.dim(r.events + ' calls')}`,
|
|
376
|
+
].join('\n'), limit);
|
|
377
|
+
if (view === 'sessions-full' || view === 'session-full') return renderFullList('Longest Active Sessions — Full Names', stats.topSessions, (r, i) => [
|
|
378
|
+
` ${pc.dim(String(i + 1).padStart(2, '0') + '.')} ${pc.white(r.project || r.cwd || r.key || 'unknown')}`,
|
|
379
|
+
` ${pc.cyan(ms(sessionDuration(r)))} ${pc.dim(r.turns + ' turns')} ${pc.dim(r.toolCalls + ' tools')}`,
|
|
380
|
+
` ${pc.dim(r.file || '')}`,
|
|
381
|
+
].join('\n'), limit);
|
|
382
|
+
if (view === 'tasks-full' || view === 'task-full') return renderFullList('Longest Tasks — Full Prompts', stats.topTasks || [], (r, i) => [
|
|
383
|
+
` ${pc.dim(String(i + 1).padStart(2, '0') + '.')} ${pc.cyan(ms(taskDuration(r)))} ${pc.magenta(r.toolCalls + ' tools')} ${pc.dim(dayOf(r.start))}`,
|
|
384
|
+
` ${pc.white(indentWrap(r.title || r.project || r.session || 'unknown'))}`,
|
|
385
|
+
` ${pc.dim(r.project || '')}`,
|
|
386
|
+
].join('\n'), limit);
|
|
325
387
|
const tables = {
|
|
326
388
|
models: ['Top Models', stats.byModel, [
|
|
327
389
|
{ width: 26, align: 'left', get: r => pc.white(r.key) },
|
|
@@ -355,14 +417,21 @@ function renderFocusedView(stats, opts = {}) {
|
|
|
355
417
|
{ width: 10, align: 'right', get: r => pc.cyan(String(r.events)) },
|
|
356
418
|
{ width: 8, align: 'left', get: () => pc.dim('calls') },
|
|
357
419
|
]],
|
|
358
|
-
sessions: ['Longest
|
|
420
|
+
sessions: ['Longest Active Sessions', stats.topSessions, [
|
|
359
421
|
{ width: 6, align: 'left', get: r => pc.dim(sessionDay(r)) },
|
|
360
|
-
{ width:
|
|
422
|
+
{ width: 30, align: 'left', get: r => pc.white(sessionLabel(r, 30)) },
|
|
423
|
+
{ width: 9, align: 'right', get: r => pc.cyan(ms(sessionDuration(r))) },
|
|
361
424
|
{ width: 8, align: 'right', get: r => pc.cyan(String(r.turns)) },
|
|
362
425
|
{ width: 8, align: 'left', get: () => pc.dim('turns') },
|
|
363
426
|
{ width: 8, align: 'right', get: r => pc.cyan(String(r.toolCalls)) },
|
|
364
427
|
{ width: 8, align: 'left', get: () => pc.dim('tools') },
|
|
365
428
|
]],
|
|
429
|
+
tasks: ['Longest Tasks', stats.topTasks || [], [
|
|
430
|
+
{ width: 6, align: 'left', get: r => pc.dim(dayOf(r.start).slice(5)) },
|
|
431
|
+
{ width: 9, align: 'right', get: r => pc.cyan(ms(taskDuration(r))) },
|
|
432
|
+
{ width: 11, align: 'right', get: r => pc.magenta(`${r.toolCalls} tools`) },
|
|
433
|
+
{ width: 36, align: 'left', get: r => pc.white(taskLabel(r, 36)) },
|
|
434
|
+
]],
|
|
366
435
|
daily: ['Daily Usage', [...stats.byDay].reverse(), [
|
|
367
436
|
{ width: 12, align: 'left', get: r => pc.white(r.key) },
|
|
368
437
|
{ width: 10, align: 'right', get: r => pc.cyan(fmtTokens(r.total)) },
|
|
@@ -385,6 +454,8 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
385
454
|
const topModel = stats.byModel[0]?.key || 'unknown';
|
|
386
455
|
const peak = stats.byDay.reduce((a,b) => b.total > (a?.total||0) ? b : a, null);
|
|
387
456
|
const streaks = calcStreaks(stats.byDay);
|
|
457
|
+
const longestSession = stats.topSessions[0] || null;
|
|
458
|
+
const longestTask = stats.topTasks?.[0] || null;
|
|
388
459
|
const lines = [];
|
|
389
460
|
|
|
390
461
|
// ── Header ────────────────────────────────────────────────────────────
|
|
@@ -415,8 +486,9 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
415
486
|
for (const c of cards) {
|
|
416
487
|
const vPad = Math.max(0, Math.floor((cardW - c.value.length) / 2));
|
|
417
488
|
const lPad = Math.max(0, Math.floor((cardW - c.label.length) / 2));
|
|
418
|
-
const
|
|
419
|
-
const
|
|
489
|
+
const color = c.color || pc.white;
|
|
490
|
+
const vContent = ' '.repeat(vPad) + pc.bold(color(c.value));
|
|
491
|
+
const lContent = ' '.repeat(lPad) + pc.dim(color(c.label));
|
|
420
492
|
// Pad to cardW visible chars
|
|
421
493
|
vStr += ansiPadEnd(vContent, cardW);
|
|
422
494
|
lStr += ansiPadEnd(lContent, cardW);
|
|
@@ -443,6 +515,19 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
443
515
|
lines.push(v2);
|
|
444
516
|
lines.push(l2);
|
|
445
517
|
|
|
518
|
+
// ── Duration Cards ────────────────────────────────────────────────────
|
|
519
|
+
lines.push('');
|
|
520
|
+
const cards3 = [
|
|
521
|
+
statCard(longestSession ? ms(sessionDuration(longestSession)) : '-', 'Active Session', pc.cyan),
|
|
522
|
+
statCard(longestSession ? String(longestSession.turns) : '-', 'Turns in Session', pc.cyan),
|
|
523
|
+
statCard(longestTask ? ms(taskDuration(longestTask)) : '-', 'Longest Task', pc.magenta),
|
|
524
|
+
statCard(longestTask ? String(longestTask.toolCalls) : '-', 'Tools in Task', pc.magenta),
|
|
525
|
+
statCard(stats.mostSubagentsSession ? String(stats.mostSubagentsSession.subagentCalls) : '0', 'Max Subagents', pc.blue),
|
|
526
|
+
];
|
|
527
|
+
const [v3, l3] = buildCardLines(cards3);
|
|
528
|
+
lines.push(v3);
|
|
529
|
+
lines.push(l3);
|
|
530
|
+
|
|
446
531
|
// ── Info line ─────────────────────────────────────────────────────────
|
|
447
532
|
lines.push('');
|
|
448
533
|
const infoText = `Peak: ${peak?.key || '-'} · ${streaks.quietDays} quiet days · ${stats.totals.events.toLocaleString()} events${stats.since ? ` · since ${stats.since}` : ''}`;
|
|
@@ -490,9 +575,10 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
490
575
|
// ── Main Session Table ──────────────────────────────────────────────────
|
|
491
576
|
if (stats.topSessions.length) {
|
|
492
577
|
lines.push('');
|
|
493
|
-
lines.push(renderTable('Longest
|
|
578
|
+
lines.push(renderTable('Longest Active Sessions', stats.topSessions.slice(0, 5), [
|
|
494
579
|
{ width: 6, align: 'left', get: r => pc.dim(sessionDay(r)) },
|
|
495
|
-
{ width:
|
|
580
|
+
{ width: 28, align: 'left', get: r => pc.white(sessionLabel(r, 28)) },
|
|
581
|
+
{ width: 9, align: 'right', get: r => pc.cyan(ms(sessionDuration(r))) },
|
|
496
582
|
{ width: 8, align: 'right', get: r => pc.cyan(String(r.turns)) },
|
|
497
583
|
{ width: 8, align: 'left', get: r => pc.dim('turns') },
|
|
498
584
|
{ width: 8, align: 'right', get: r => pc.cyan(String(r.toolCalls)) },
|
|
@@ -500,6 +586,27 @@ export function renderTakomiStats(stats, opts = {}) {
|
|
|
500
586
|
]));
|
|
501
587
|
}
|
|
502
588
|
|
|
589
|
+
// ── Longest Tasks ──────────────────────────────────────────────────────
|
|
590
|
+
if (stats.topTasks?.length) {
|
|
591
|
+
lines.push('');
|
|
592
|
+
lines.push(renderTable('Longest Tasks', stats.topTasks.slice(0, 5), [
|
|
593
|
+
{ width: 6, align: 'left', get: r => pc.dim(dayOf(r.start).slice(5)) },
|
|
594
|
+
{ width: 9, align: 'right', get: r => pc.cyan(ms(taskDuration(r))) },
|
|
595
|
+
{ width: 11, align: 'right', get: r => pc.magenta(`${r.toolCalls} tools`) },
|
|
596
|
+
{ width: 34, align: 'left', get: r => pc.white(taskLabel(r, 34)) },
|
|
597
|
+
]));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ── Longest Subagent Run ───────────────────────────────────────────────
|
|
601
|
+
if (stats.longestRun) {
|
|
602
|
+
lines.push('');
|
|
603
|
+
lines.push(renderTable('Longest Subagent Run', [stats.longestRun], [
|
|
604
|
+
{ width: 22, align: 'left', get: r => pc.white(r.agent || 'unknown') },
|
|
605
|
+
{ width: 10, align: 'right', get: r => pc.cyan(ms(+r.duration || 0)) },
|
|
606
|
+
{ width: 30, align: 'left', get: r => pc.dim(runLabel(r, 30)) },
|
|
607
|
+
]));
|
|
608
|
+
}
|
|
609
|
+
|
|
503
610
|
// ── Tools Table ─────────────────────────────────────────────────────────
|
|
504
611
|
if (stats.byTool.length) {
|
|
505
612
|
lines.push('');
|