trickle-cli 0.1.197 → 0.1.199

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.
@@ -279,8 +279,7 @@ async function runCommand(command, opts) {
279
279
  if (!backendProc) {
280
280
  // Fall back to local/offline mode instead of exiting
281
281
  localMode = true;
282
- console.log(chalk_1.default.yellow(`\n Backend not availableusing local mode (offline)`));
283
- console.log(chalk_1.default.gray(" Observations will be saved to .trickle/observations.jsonl"));
282
+ // Silent for first-time users — local mode is the default experience
284
283
  }
285
284
  }
286
285
  // Detect language and inject instrumentation
@@ -400,13 +399,9 @@ async function executeSingleRun(instrumentedCommand, env, opts, singleFile, loca
400
399
  await autoCloudPush();
401
400
  // Generate post-run summary for AI agents
402
401
  (0, summary_1.writeRunSummary)({ exitCode, command: instrumentedCommand });
403
- // Next steps hint
404
402
  console.log("");
405
- console.log(chalk_1.default.bold(" Next steps:"));
406
- console.log(chalk_1.default.gray(" trickle summary ") + "full analysis (errors, queries, root causes)");
407
- console.log(chalk_1.default.gray(" trickle explain <file> ") + "understand a file (functions, call graph, data flow)");
408
- console.log(chalk_1.default.gray(" trickle flamegraph ") + "performance hotspots");
409
- console.log(chalk_1.default.gray(" trickle test ") + "run tests with observability");
403
+ console.log(chalk_1.default.gray(" trickle summary ") + "full analysis");
404
+ console.log(chalk_1.default.gray(" trickle why ") + "trace any error to root cause");
410
405
  console.log("");
411
406
  return exitCode;
412
407
  }
@@ -562,8 +557,19 @@ async function autoCloudPush() {
562
557
  try {
563
558
  const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
564
559
  if (config.url && config.token) {
565
- const { cloudPush } = await Promise.resolve().then(() => __importStar(require("./cloud")));
566
- await cloudPush();
560
+ // Suppress console output for auto-push to avoid noise
561
+ const origLog = console.log;
562
+ const origErr = console.error;
563
+ console.log = () => { };
564
+ console.error = () => { };
565
+ try {
566
+ const { cloudPush } = await Promise.resolve().then(() => __importStar(require("./cloud")));
567
+ await cloudPush();
568
+ }
569
+ finally {
570
+ console.log = origLog;
571
+ console.error = origErr;
572
+ }
567
573
  }
568
574
  }
569
575
  catch { }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * trickle summarize — Compress verbose agent traces into key decision points.
3
+ *
4
+ * Reads agents.jsonl, llm.jsonl, mcp.jsonl and produces a concise
5
+ * narrative of what the agent did, why, and at what cost.
6
+ */
7
+ export declare function summarizeCommand(opts: {
8
+ json?: boolean;
9
+ }): void;
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /**
3
+ * trickle summarize — Compress verbose agent traces into key decision points.
4
+ *
5
+ * Reads agents.jsonl, llm.jsonl, mcp.jsonl and produces a concise
6
+ * narrative of what the agent did, why, and at what cost.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.summarizeCommand = summarizeCommand;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const chalk_1 = __importDefault(require("chalk"));
49
+ function readJsonl(fp) {
50
+ if (!fs.existsSync(fp))
51
+ return [];
52
+ return fs.readFileSync(fp, 'utf-8').split('\n').filter(Boolean)
53
+ .map(l => { try {
54
+ return JSON.parse(l);
55
+ }
56
+ catch {
57
+ return null;
58
+ } }).filter(Boolean);
59
+ }
60
+ function summarizeCommand(opts) {
61
+ const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
62
+ const agentEvents = readJsonl(path.join(dir, 'agents.jsonl'));
63
+ const llmCalls = readJsonl(path.join(dir, 'llm.jsonl'));
64
+ const mcpCalls = readJsonl(path.join(dir, 'mcp.jsonl'));
65
+ const errors = readJsonl(path.join(dir, 'errors.jsonl'));
66
+ if (agentEvents.length === 0 && llmCalls.length === 0 && mcpCalls.length === 0) {
67
+ console.log(chalk_1.default.yellow(' No agent, LLM, or MCP data to summarize.'));
68
+ return;
69
+ }
70
+ const summary = buildSummary(agentEvents, llmCalls, mcpCalls, errors);
71
+ if (opts.json) {
72
+ console.log(JSON.stringify(summary, null, 2));
73
+ return;
74
+ }
75
+ console.log('');
76
+ console.log(chalk_1.default.bold(' trickle summarize'));
77
+ console.log(chalk_1.default.gray(' ' + '─'.repeat(60)));
78
+ // One-line overview
79
+ console.log(` ${summary.overview}`);
80
+ console.log('');
81
+ // Key decisions
82
+ if (summary.decisions.length > 0) {
83
+ console.log(chalk_1.default.bold(' Key Decisions'));
84
+ for (const d of summary.decisions) {
85
+ const icon = d.type === 'error' ? chalk_1.default.red('✗') : d.type === 'tool' ? chalk_1.default.green('⚙') : d.type === 'llm' ? chalk_1.default.magenta('✦') : chalk_1.default.blue('→');
86
+ console.log(` ${icon} ${d.description}`);
87
+ }
88
+ console.log('');
89
+ }
90
+ // Cost breakdown
91
+ if (summary.cost.total > 0) {
92
+ console.log(chalk_1.default.bold(' Cost'));
93
+ console.log(` ${chalk_1.default.green('$' + summary.cost.total.toFixed(4))} total across ${summary.cost.llmCalls} LLM calls (${formatTokens(summary.cost.tokens)} tokens)`);
94
+ if (summary.cost.mostExpensive) {
95
+ console.log(chalk_1.default.gray(` Most expensive: $${summary.cost.mostExpensive.cost.toFixed(4)} — ${summary.cost.mostExpensive.description}`));
96
+ }
97
+ console.log('');
98
+ }
99
+ // Issues
100
+ if (summary.issues.length > 0) {
101
+ console.log(chalk_1.default.bold(' Issues'));
102
+ for (const issue of summary.issues) {
103
+ console.log(` ${chalk_1.default.red('!')} ${issue}`);
104
+ }
105
+ console.log('');
106
+ }
107
+ console.log(chalk_1.default.gray(' ' + '─'.repeat(60)));
108
+ console.log('');
109
+ }
110
+ function buildSummary(agentEvents, llmCalls, mcpCalls, errors) {
111
+ const decisions = [];
112
+ const issues = [];
113
+ // Extract key agent decisions
114
+ const crewStarts = agentEvents.filter(e => e.event === 'crew_start' || e.event === 'chain_start');
115
+ const crewEnds = agentEvents.filter(e => e.event === 'crew_end' || e.event === 'chain_end');
116
+ const toolStarts = agentEvents.filter(e => e.event === 'tool_start');
117
+ const toolEnds = agentEvents.filter(e => e.event === 'tool_end');
118
+ const toolErrors = agentEvents.filter(e => e.event === 'tool_error');
119
+ const agentActions = agentEvents.filter(e => e.event === 'action');
120
+ // Tools used
121
+ const toolNames = [...new Set(toolStarts.map(e => e.tool).filter(Boolean))];
122
+ if (toolNames.length > 0) {
123
+ decisions.push({ type: 'tool', description: `Used ${toolNames.length} tools: ${toolNames.join(', ')}` });
124
+ }
125
+ // Agent reasoning (from action events with thoughts)
126
+ for (const a of agentActions.slice(0, 3)) {
127
+ if (a.thought) {
128
+ decisions.push({ type: 'reasoning', description: `Thought: "${truncate(a.thought, 80)}"` });
129
+ }
130
+ }
131
+ // Handoffs
132
+ const handoffs = agentEvents.filter(e => e.tool === 'handoff');
133
+ for (const h of handoffs) {
134
+ decisions.push({ type: 'handoff', description: `Handoff: ${truncate(h.toolInput || '', 80)}` });
135
+ }
136
+ // LLM calls summary
137
+ const models = [...new Set(llmCalls.map(c => `${c.provider}/${c.model}`))];
138
+ if (models.length > 0) {
139
+ decisions.push({ type: 'llm', description: `${llmCalls.length} LLM calls using ${models.join(', ')}` });
140
+ }
141
+ // MCP tool calls
142
+ const mcpTools = [...new Set(mcpCalls.filter(c => c.tool !== '__list_tools').map(c => c.tool))];
143
+ if (mcpTools.length > 0) {
144
+ decisions.push({ type: 'tool', description: `${mcpCalls.length} MCP tool calls: ${mcpTools.join(', ')}` });
145
+ }
146
+ // Errors
147
+ for (const te of toolErrors.slice(0, 2)) {
148
+ decisions.push({ type: 'error', description: `Tool "${te.tool}" failed: ${truncate(te.error || '', 60)}` });
149
+ }
150
+ for (const err of errors.slice(0, 2)) {
151
+ issues.push(`${err.type || 'Error'}: ${truncate(err.message || '', 80)}`);
152
+ }
153
+ // Detect patterns
154
+ const toolNameList = toolStarts.map(e => e.tool || '');
155
+ for (let i = 0; i < toolNameList.length - 2; i++) {
156
+ if (toolNameList[i] === toolNameList[i + 1] && toolNameList[i] === toolNameList[i + 2]) {
157
+ issues.push(`Tool "${toolNameList[i]}" called 3+ times — possible retry loop`);
158
+ break;
159
+ }
160
+ }
161
+ const llmErrors = llmCalls.filter(c => c.error);
162
+ if (llmErrors.length > 0) {
163
+ issues.push(`${llmErrors.length}/${llmCalls.length} LLM calls failed`);
164
+ }
165
+ // Cost
166
+ const totalCost = llmCalls.reduce((s, c) => s + (c.estimatedCostUsd || 0), 0);
167
+ const totalTokens = llmCalls.reduce((s, c) => s + (c.totalTokens || 0), 0);
168
+ const mostExpensiveCall = llmCalls.length > 0
169
+ ? llmCalls.reduce((max, c) => (c.estimatedCostUsd || 0) > (max.estimatedCostUsd || 0) ? c : max, llmCalls[0])
170
+ : null;
171
+ // Duration
172
+ const allTimestamps = [
173
+ ...agentEvents.map(e => e.timestamp),
174
+ ...llmCalls.map(c => c.timestamp),
175
+ ...mcpCalls.map(c => c.timestamp),
176
+ ].filter(Boolean).sort();
177
+ const duration = allTimestamps.length >= 2 ? allTimestamps[allTimestamps.length - 1] - allTimestamps[0] : 0;
178
+ // Build overview
179
+ const parts = [];
180
+ if (crewStarts.length > 0) {
181
+ const frameworks = [...new Set(agentEvents.map(e => e.framework).filter(Boolean))];
182
+ parts.push(`${crewStarts.length} agent run(s)${frameworks.length > 0 ? ` (${frameworks.join(', ')})` : ''}`);
183
+ }
184
+ if (llmCalls.length > 0)
185
+ parts.push(`${llmCalls.length} LLM calls ($${totalCost.toFixed(4)})`);
186
+ if (toolNames.length > 0)
187
+ parts.push(`${toolStarts.length} tool calls`);
188
+ if (mcpTools.length > 0)
189
+ parts.push(`${mcpCalls.length} MCP calls`);
190
+ if (toolErrors.length > 0)
191
+ parts.push(`${toolErrors.length} tool errors`);
192
+ if (duration > 0)
193
+ parts.push(formatDuration(duration));
194
+ const completed = crewEnds.length > 0 ? 'completed' : crewStarts.length > 0 ? 'started' : '';
195
+ const overview = parts.length > 0
196
+ ? `${completed ? completed.charAt(0).toUpperCase() + completed.slice(1) + ': ' : ''}${parts.join(', ')}`
197
+ : 'No significant events captured';
198
+ return {
199
+ overview, decisions, issues, duration,
200
+ cost: {
201
+ total: totalCost, llmCalls: llmCalls.length, tokens: totalTokens,
202
+ mostExpensive: mostExpensiveCall && mostExpensiveCall.estimatedCostUsd > 0
203
+ ? { cost: mostExpensiveCall.estimatedCostUsd, description: `${mostExpensiveCall.model}: "${truncate(mostExpensiveCall.inputPreview || '', 50)}"` }
204
+ : null,
205
+ },
206
+ };
207
+ }
208
+ function truncate(s, len) {
209
+ return s.length > len ? s.substring(0, len) + '...' : s;
210
+ }
211
+ function formatTokens(n) {
212
+ if (n >= 1_000_000)
213
+ return (n / 1_000_000).toFixed(1) + 'M';
214
+ if (n >= 1_000)
215
+ return (n / 1_000).toFixed(1) + 'K';
216
+ return String(n);
217
+ }
218
+ function formatDuration(ms) {
219
+ if (ms >= 60_000)
220
+ return (ms / 60_000).toFixed(1) + 'min';
221
+ if (ms >= 1_000)
222
+ return (ms / 1_000).toFixed(1) + 's';
223
+ return ms + 'ms';
224
+ }
package/dist/index.js CHANGED
@@ -920,6 +920,15 @@ program
920
920
  const { whyCommand } = await Promise.resolve().then(() => __importStar(require("./commands/why")));
921
921
  whyCommand(query, opts);
922
922
  });
923
+ // trickle summarize
924
+ program
925
+ .command("summarize")
926
+ .description("Compress agent traces into key decisions — what happened, why, at what cost")
927
+ .option("--json", "Output structured JSON")
928
+ .action(async (opts) => {
929
+ const { summarizeCommand } = await Promise.resolve().then(() => __importStar(require("./commands/summarize")));
930
+ summarizeCommand(opts);
931
+ });
923
932
  // trickle cleanup
924
933
  program
925
934
  .command("cleanup")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-cli",
3
- "version": "0.1.197",
3
+ "version": "0.1.199",
4
4
  "description": "CLI for trickle runtime type observability",
5
5
  "bin": {
6
6
  "trickle": "dist/index.js"
@@ -290,16 +290,7 @@ export async function runCommand(
290
290
  if (!backendProc) {
291
291
  // Fall back to local/offline mode instead of exiting
292
292
  localMode = true;
293
- console.log(
294
- chalk.yellow(
295
- `\n Backend not available — using local mode (offline)`,
296
- ),
297
- );
298
- console.log(
299
- chalk.gray(
300
- " Observations will be saved to .trickle/observations.jsonl",
301
- ),
302
- );
293
+ // Silent for first-time users — local mode is the default experience
303
294
  }
304
295
  }
305
296
 
@@ -453,13 +444,9 @@ async function executeSingleRun(
453
444
  // Generate post-run summary for AI agents
454
445
  writeRunSummary({ exitCode, command: instrumentedCommand });
455
446
 
456
- // Next steps hint
457
447
  console.log("");
458
- console.log(chalk.bold(" Next steps:"));
459
- console.log(chalk.gray(" trickle summary ") + "full analysis (errors, queries, root causes)");
460
- console.log(chalk.gray(" trickle explain <file> ") + "understand a file (functions, call graph, data flow)");
461
- console.log(chalk.gray(" trickle flamegraph ") + "performance hotspots");
462
- console.log(chalk.gray(" trickle test ") + "run tests with observability");
448
+ console.log(chalk.gray(" trickle summary ") + "full analysis");
449
+ console.log(chalk.gray(" trickle why ") + "trace any error to root cause");
463
450
  console.log("");
464
451
 
465
452
  return exitCode;
@@ -632,8 +619,18 @@ async function autoCloudPush(): Promise<void> {
632
619
  try {
633
620
  const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
634
621
  if (config.url && config.token) {
635
- const { cloudPush } = await import("./cloud");
636
- await cloudPush();
622
+ // Suppress console output for auto-push to avoid noise
623
+ const origLog = console.log;
624
+ const origErr = console.error;
625
+ console.log = () => {};
626
+ console.error = () => {};
627
+ try {
628
+ const { cloudPush } = await import("./cloud");
629
+ await cloudPush();
630
+ } finally {
631
+ console.log = origLog;
632
+ console.error = origErr;
633
+ }
637
634
  }
638
635
  } catch {}
639
636
  }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * trickle summarize — Compress verbose agent traces into key decision points.
3
+ *
4
+ * Reads agents.jsonl, llm.jsonl, mcp.jsonl and produces a concise
5
+ * narrative of what the agent did, why, and at what cost.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import chalk from 'chalk';
11
+
12
+ function readJsonl(fp: string): any[] {
13
+ if (!fs.existsSync(fp)) return [];
14
+ return fs.readFileSync(fp, 'utf-8').split('\n').filter(Boolean)
15
+ .map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
16
+ }
17
+
18
+ export function summarizeCommand(opts: { json?: boolean }): void {
19
+ const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
20
+ const agentEvents = readJsonl(path.join(dir, 'agents.jsonl'));
21
+ const llmCalls = readJsonl(path.join(dir, 'llm.jsonl'));
22
+ const mcpCalls = readJsonl(path.join(dir, 'mcp.jsonl'));
23
+ const errors = readJsonl(path.join(dir, 'errors.jsonl'));
24
+
25
+ if (agentEvents.length === 0 && llmCalls.length === 0 && mcpCalls.length === 0) {
26
+ console.log(chalk.yellow(' No agent, LLM, or MCP data to summarize.'));
27
+ return;
28
+ }
29
+
30
+ const summary = buildSummary(agentEvents, llmCalls, mcpCalls, errors);
31
+
32
+ if (opts.json) {
33
+ console.log(JSON.stringify(summary, null, 2));
34
+ return;
35
+ }
36
+
37
+ console.log('');
38
+ console.log(chalk.bold(' trickle summarize'));
39
+ console.log(chalk.gray(' ' + '─'.repeat(60)));
40
+
41
+ // One-line overview
42
+ console.log(` ${summary.overview}`);
43
+ console.log('');
44
+
45
+ // Key decisions
46
+ if (summary.decisions.length > 0) {
47
+ console.log(chalk.bold(' Key Decisions'));
48
+ for (const d of summary.decisions) {
49
+ const icon = d.type === 'error' ? chalk.red('✗') : d.type === 'tool' ? chalk.green('⚙') : d.type === 'llm' ? chalk.magenta('✦') : chalk.blue('→');
50
+ console.log(` ${icon} ${d.description}`);
51
+ }
52
+ console.log('');
53
+ }
54
+
55
+ // Cost breakdown
56
+ if (summary.cost.total > 0) {
57
+ console.log(chalk.bold(' Cost'));
58
+ console.log(` ${chalk.green('$' + summary.cost.total.toFixed(4))} total across ${summary.cost.llmCalls} LLM calls (${formatTokens(summary.cost.tokens)} tokens)`);
59
+ if (summary.cost.mostExpensive) {
60
+ console.log(chalk.gray(` Most expensive: $${summary.cost.mostExpensive.cost.toFixed(4)} — ${summary.cost.mostExpensive.description}`));
61
+ }
62
+ console.log('');
63
+ }
64
+
65
+ // Issues
66
+ if (summary.issues.length > 0) {
67
+ console.log(chalk.bold(' Issues'));
68
+ for (const issue of summary.issues) {
69
+ console.log(` ${chalk.red('!')} ${issue}`);
70
+ }
71
+ console.log('');
72
+ }
73
+
74
+ console.log(chalk.gray(' ' + '─'.repeat(60)));
75
+ console.log('');
76
+ }
77
+
78
+ interface TraceSummary {
79
+ overview: string;
80
+ decisions: Array<{ type: string; description: string }>;
81
+ cost: { total: number; llmCalls: number; tokens: number; mostExpensive: { cost: number; description: string } | null };
82
+ issues: string[];
83
+ duration: number;
84
+ }
85
+
86
+ function buildSummary(agentEvents: any[], llmCalls: any[], mcpCalls: any[], errors: any[]): TraceSummary {
87
+ const decisions: TraceSummary['decisions'] = [];
88
+ const issues: string[] = [];
89
+
90
+ // Extract key agent decisions
91
+ const crewStarts = agentEvents.filter(e => e.event === 'crew_start' || e.event === 'chain_start');
92
+ const crewEnds = agentEvents.filter(e => e.event === 'crew_end' || e.event === 'chain_end');
93
+ const toolStarts = agentEvents.filter(e => e.event === 'tool_start');
94
+ const toolEnds = agentEvents.filter(e => e.event === 'tool_end');
95
+ const toolErrors = agentEvents.filter(e => e.event === 'tool_error');
96
+ const agentActions = agentEvents.filter(e => e.event === 'action');
97
+
98
+ // Tools used
99
+ const toolNames = [...new Set(toolStarts.map(e => e.tool).filter(Boolean))];
100
+ if (toolNames.length > 0) {
101
+ decisions.push({ type: 'tool', description: `Used ${toolNames.length} tools: ${toolNames.join(', ')}` });
102
+ }
103
+
104
+ // Agent reasoning (from action events with thoughts)
105
+ for (const a of agentActions.slice(0, 3)) {
106
+ if (a.thought) {
107
+ decisions.push({ type: 'reasoning', description: `Thought: "${truncate(a.thought, 80)}"` });
108
+ }
109
+ }
110
+
111
+ // Handoffs
112
+ const handoffs = agentEvents.filter(e => e.tool === 'handoff');
113
+ for (const h of handoffs) {
114
+ decisions.push({ type: 'handoff', description: `Handoff: ${truncate(h.toolInput || '', 80)}` });
115
+ }
116
+
117
+ // LLM calls summary
118
+ const models = [...new Set(llmCalls.map(c => `${c.provider}/${c.model}`))];
119
+ if (models.length > 0) {
120
+ decisions.push({ type: 'llm', description: `${llmCalls.length} LLM calls using ${models.join(', ')}` });
121
+ }
122
+
123
+ // MCP tool calls
124
+ const mcpTools = [...new Set(mcpCalls.filter(c => c.tool !== '__list_tools').map(c => c.tool))];
125
+ if (mcpTools.length > 0) {
126
+ decisions.push({ type: 'tool', description: `${mcpCalls.length} MCP tool calls: ${mcpTools.join(', ')}` });
127
+ }
128
+
129
+ // Errors
130
+ for (const te of toolErrors.slice(0, 2)) {
131
+ decisions.push({ type: 'error', description: `Tool "${te.tool}" failed: ${truncate(te.error || '', 60)}` });
132
+ }
133
+ for (const err of errors.slice(0, 2)) {
134
+ issues.push(`${err.type || 'Error'}: ${truncate(err.message || '', 80)}`);
135
+ }
136
+
137
+ // Detect patterns
138
+ const toolNameList = toolStarts.map(e => e.tool || '');
139
+ for (let i = 0; i < toolNameList.length - 2; i++) {
140
+ if (toolNameList[i] === toolNameList[i + 1] && toolNameList[i] === toolNameList[i + 2]) {
141
+ issues.push(`Tool "${toolNameList[i]}" called 3+ times — possible retry loop`);
142
+ break;
143
+ }
144
+ }
145
+
146
+ const llmErrors = llmCalls.filter(c => c.error);
147
+ if (llmErrors.length > 0) {
148
+ issues.push(`${llmErrors.length}/${llmCalls.length} LLM calls failed`);
149
+ }
150
+
151
+ // Cost
152
+ const totalCost = llmCalls.reduce((s: number, c: any) => s + (c.estimatedCostUsd || 0), 0);
153
+ const totalTokens = llmCalls.reduce((s: number, c: any) => s + (c.totalTokens || 0), 0);
154
+ const mostExpensiveCall = llmCalls.length > 0
155
+ ? llmCalls.reduce((max: any, c: any) => (c.estimatedCostUsd || 0) > (max.estimatedCostUsd || 0) ? c : max, llmCalls[0])
156
+ : null;
157
+
158
+ // Duration
159
+ const allTimestamps = [
160
+ ...agentEvents.map(e => e.timestamp),
161
+ ...llmCalls.map(c => c.timestamp),
162
+ ...mcpCalls.map(c => c.timestamp),
163
+ ].filter(Boolean).sort();
164
+ const duration = allTimestamps.length >= 2 ? allTimestamps[allTimestamps.length - 1] - allTimestamps[0] : 0;
165
+
166
+ // Build overview
167
+ const parts: string[] = [];
168
+ if (crewStarts.length > 0) {
169
+ const frameworks = [...new Set(agentEvents.map(e => e.framework).filter(Boolean))];
170
+ parts.push(`${crewStarts.length} agent run(s)${frameworks.length > 0 ? ` (${frameworks.join(', ')})` : ''}`);
171
+ }
172
+ if (llmCalls.length > 0) parts.push(`${llmCalls.length} LLM calls ($${totalCost.toFixed(4)})`);
173
+ if (toolNames.length > 0) parts.push(`${toolStarts.length} tool calls`);
174
+ if (mcpTools.length > 0) parts.push(`${mcpCalls.length} MCP calls`);
175
+ if (toolErrors.length > 0) parts.push(`${toolErrors.length} tool errors`);
176
+ if (duration > 0) parts.push(formatDuration(duration));
177
+
178
+ const completed = crewEnds.length > 0 ? 'completed' : crewStarts.length > 0 ? 'started' : '';
179
+ const overview = parts.length > 0
180
+ ? `${completed ? completed.charAt(0).toUpperCase() + completed.slice(1) + ': ' : ''}${parts.join(', ')}`
181
+ : 'No significant events captured';
182
+
183
+ return {
184
+ overview, decisions, issues, duration,
185
+ cost: {
186
+ total: totalCost, llmCalls: llmCalls.length, tokens: totalTokens,
187
+ mostExpensive: mostExpensiveCall && mostExpensiveCall.estimatedCostUsd > 0
188
+ ? { cost: mostExpensiveCall.estimatedCostUsd, description: `${mostExpensiveCall.model}: "${truncate(mostExpensiveCall.inputPreview || '', 50)}"` }
189
+ : null,
190
+ },
191
+ };
192
+ }
193
+
194
+ function truncate(s: string, len: number): string {
195
+ return s.length > len ? s.substring(0, len) + '...' : s;
196
+ }
197
+
198
+ function formatTokens(n: number): string {
199
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
200
+ if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
201
+ return String(n);
202
+ }
203
+
204
+ function formatDuration(ms: number): string {
205
+ if (ms >= 60_000) return (ms / 60_000).toFixed(1) + 'min';
206
+ if (ms >= 1_000) return (ms / 1_000).toFixed(1) + 's';
207
+ return ms + 'ms';
208
+ }
package/src/index.ts CHANGED
@@ -953,6 +953,16 @@ program
953
953
  whyCommand(query, opts);
954
954
  });
955
955
 
956
+ // trickle summarize
957
+ program
958
+ .command("summarize")
959
+ .description("Compress agent traces into key decisions — what happened, why, at what cost")
960
+ .option("--json", "Output structured JSON")
961
+ .action(async (opts) => {
962
+ const { summarizeCommand } = await import("./commands/summarize");
963
+ summarizeCommand(opts);
964
+ });
965
+
956
966
  // trickle cleanup
957
967
  program
958
968
  .command("cleanup")