trickle-cli 0.1.198 → 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.
@@ -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.198",
3
+ "version": "0.1.199",
4
4
  "description": "CLI for trickle runtime type observability",
5
5
  "bin": {
6
6
  "trickle": "dist/index.js"
@@ -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")