trickle-cli 0.1.200 → 0.1.202
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/dist/commands/demo.js +9 -3
- package/dist/commands/playback.d.ts +11 -0
- package/dist/commands/playback.js +167 -0
- package/dist/index.js +9 -0
- package/package.json +1 -1
- package/src/commands/demo.ts +9 -3
- package/src/commands/playback.ts +137 -0
- package/src/index.ts +10 -0
package/dist/commands/demo.js
CHANGED
|
@@ -98,7 +98,7 @@ async function runDemo() {
|
|
|
98
98
|
console.log('');
|
|
99
99
|
console.log(chalk_1.default.bold.cyan(' ╔══════════════════════════════════════════╗'));
|
|
100
100
|
console.log(chalk_1.default.bold.cyan(' ║ trickle — Live Demo ║'));
|
|
101
|
-
console.log(chalk_1.default.bold.cyan(' ║
|
|
101
|
+
console.log(chalk_1.default.bold.cyan(' ║ Zero-Code Observability in 60 Seconds ║'));
|
|
102
102
|
console.log(chalk_1.default.bold.cyan(' ╚══════════════════════════════════════════╝'));
|
|
103
103
|
// Step 1: Create demo project
|
|
104
104
|
printSection('1. Creating demo project');
|
|
@@ -208,9 +208,15 @@ async function runDemo() {
|
|
|
208
208
|
console.log(chalk_1.default.bold.cyan(' ║ ║'));
|
|
209
209
|
console.log(chalk_1.default.bold.cyan(' ║ npm install -g trickle-cli ║'));
|
|
210
210
|
console.log(chalk_1.default.bold.cyan(' ║ trickle init ║'));
|
|
211
|
-
console.log(chalk_1.default.bold.cyan(' ║ trickle run
|
|
211
|
+
console.log(chalk_1.default.bold.cyan(' ║ trickle run node app.js ║'));
|
|
212
212
|
console.log(chalk_1.default.bold.cyan(' ║ ║'));
|
|
213
|
-
console.log(chalk_1.default.bold.cyan(' ║
|
|
213
|
+
console.log(chalk_1.default.bold.cyan(' ║ Also try: ║'));
|
|
214
|
+
console.log(chalk_1.default.bold.cyan(' ║ trickle eval — A-F grading ║'));
|
|
215
|
+
console.log(chalk_1.default.bold.cyan(' ║ trickle why — root cause ║'));
|
|
216
|
+
console.log(chalk_1.default.bold.cyan(' ║ trickle cost-report — LLM costs ║'));
|
|
217
|
+
console.log(chalk_1.default.bold.cyan(' ║ trickle summarize — trace summary ║'));
|
|
218
|
+
console.log(chalk_1.default.bold.cyan(' ║ ║'));
|
|
219
|
+
console.log(chalk_1.default.bold.cyan(' ║ 38 MCP tools | Free, local, zero-code ║'));
|
|
214
220
|
console.log(chalk_1.default.bold.cyan(' ║ github.com/yiheinchai/trickle ║'));
|
|
215
221
|
console.log(chalk_1.default.bold.cyan(' ╚══════════════════════════════════════════╝'));
|
|
216
222
|
console.log('');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trickle playback — Step-by-step replay of agent execution.
|
|
3
|
+
*
|
|
4
|
+
* Reads agents.jsonl, llm.jsonl, mcp.jsonl and plays back events
|
|
5
|
+
* in chronological order with timing. Local-first: instant replay
|
|
6
|
+
* from local JSONL files, no server needed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function playbackCommand(opts: {
|
|
9
|
+
json?: boolean;
|
|
10
|
+
speed?: string;
|
|
11
|
+
}): void;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* trickle playback — Step-by-step replay of agent execution.
|
|
4
|
+
*
|
|
5
|
+
* Reads agents.jsonl, llm.jsonl, mcp.jsonl and plays back events
|
|
6
|
+
* in chronological order with timing. Local-first: instant replay
|
|
7
|
+
* from local JSONL files, no server needed.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
43
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.playbackCommand = playbackCommand;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
50
|
+
function readJsonl(fp) {
|
|
51
|
+
if (!fs.existsSync(fp))
|
|
52
|
+
return [];
|
|
53
|
+
return fs.readFileSync(fp, 'utf-8').split('\n').filter(Boolean)
|
|
54
|
+
.map(l => { try {
|
|
55
|
+
return JSON.parse(l);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
} }).filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
function playbackCommand(opts) {
|
|
62
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
63
|
+
const events = [];
|
|
64
|
+
// Collect events from all sources
|
|
65
|
+
for (const e of readJsonl(path.join(dir, 'agents.jsonl'))) {
|
|
66
|
+
const evt = e.event || '?';
|
|
67
|
+
const name = e.chain || e.tool || '';
|
|
68
|
+
let icon = '→';
|
|
69
|
+
if (evt.includes('start'))
|
|
70
|
+
icon = '▶';
|
|
71
|
+
if (evt.includes('end'))
|
|
72
|
+
icon = '■';
|
|
73
|
+
if (evt.includes('error'))
|
|
74
|
+
icon = '✗';
|
|
75
|
+
if (evt === 'action')
|
|
76
|
+
icon = '⚒';
|
|
77
|
+
if (evt === 'finish')
|
|
78
|
+
icon = '✔';
|
|
79
|
+
let detail = '';
|
|
80
|
+
if (e.toolInput)
|
|
81
|
+
detail = `→ ${String(e.toolInput).substring(0, 80)}`;
|
|
82
|
+
if (e.output)
|
|
83
|
+
detail = `← ${String(e.output).substring(0, 80)}`;
|
|
84
|
+
if (e.thought)
|
|
85
|
+
detail = `💭 ${String(e.thought).substring(0, 80)}`;
|
|
86
|
+
if (e.error)
|
|
87
|
+
detail = `✗ ${String(e.error).substring(0, 80)}`;
|
|
88
|
+
events.push({
|
|
89
|
+
timestamp: e.timestamp || 0,
|
|
90
|
+
source: `agent:${evt}`,
|
|
91
|
+
icon,
|
|
92
|
+
title: `${name} [${evt}]`,
|
|
93
|
+
detail,
|
|
94
|
+
durationMs: e.durationMs,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
for (const c of readJsonl(path.join(dir, 'llm.jsonl'))) {
|
|
98
|
+
const err = c.error ? ` ERR: ${String(c.error).substring(0, 40)}` : '';
|
|
99
|
+
events.push({
|
|
100
|
+
timestamp: c.timestamp || 0,
|
|
101
|
+
source: 'llm',
|
|
102
|
+
icon: '✦',
|
|
103
|
+
title: `${c.provider}/${c.model}`,
|
|
104
|
+
detail: `${c.totalTokens || 0}tok ${c.estimatedCostUsd ? '$' + c.estimatedCostUsd.toFixed(4) : ''} → ${(c.inputPreview || '').substring(0, 50)}${err}`,
|
|
105
|
+
durationMs: c.durationMs,
|
|
106
|
+
cost: c.estimatedCostUsd,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
for (const m of readJsonl(path.join(dir, 'mcp.jsonl'))) {
|
|
110
|
+
if (m.tool === '__list_tools')
|
|
111
|
+
continue;
|
|
112
|
+
const dir2 = m.direction === 'outgoing' ? '→' : '←';
|
|
113
|
+
events.push({
|
|
114
|
+
timestamp: m.timestamp || 0,
|
|
115
|
+
source: 'mcp',
|
|
116
|
+
icon: dir2,
|
|
117
|
+
title: `MCP: ${m.tool}`,
|
|
118
|
+
detail: m.resultPreview ? `← ${String(m.resultPreview).substring(0, 60)}` : '',
|
|
119
|
+
durationMs: m.durationMs,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (events.length === 0) {
|
|
123
|
+
console.log(chalk_1.default.yellow(' No agent/LLM/MCP events to replay.'));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Sort chronologically
|
|
127
|
+
events.sort((a, b) => a.timestamp - b.timestamp);
|
|
128
|
+
if (opts.json) {
|
|
129
|
+
console.log(JSON.stringify(events, null, 2));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Playback
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(chalk_1.default.bold(' trickle playback'));
|
|
135
|
+
console.log(chalk_1.default.gray(' ' + '─'.repeat(60)));
|
|
136
|
+
console.log(chalk_1.default.gray(` ${events.length} events | replaying chronologically`));
|
|
137
|
+
console.log('');
|
|
138
|
+
const startTs = events[0].timestamp;
|
|
139
|
+
let cumulativeCost = 0;
|
|
140
|
+
for (let i = 0; i < events.length; i++) {
|
|
141
|
+
const e = events[i];
|
|
142
|
+
const elapsed = e.timestamp - startTs;
|
|
143
|
+
const timeStr = formatTime(elapsed);
|
|
144
|
+
const durStr = e.durationMs ? chalk_1.default.gray(` (${e.durationMs.toFixed(0)}ms)`) : '';
|
|
145
|
+
if (e.cost)
|
|
146
|
+
cumulativeCost += e.cost;
|
|
147
|
+
const iconColor = e.source === 'llm' ? chalk_1.default.magenta : e.source === 'mcp' ? chalk_1.default.green :
|
|
148
|
+
e.icon === '✗' ? chalk_1.default.red : e.icon === '✔' ? chalk_1.default.green : chalk_1.default.blue;
|
|
149
|
+
console.log(` ${chalk_1.default.gray(timeStr)} ${iconColor(e.icon)} ${chalk_1.default.bold(e.title)}${durStr}`);
|
|
150
|
+
if (e.detail) {
|
|
151
|
+
console.log(` ${chalk_1.default.gray(' ')} ${chalk_1.default.gray(e.detail)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Summary
|
|
155
|
+
const totalDuration = events[events.length - 1].timestamp - startTs;
|
|
156
|
+
console.log('');
|
|
157
|
+
console.log(chalk_1.default.gray(' ' + '─'.repeat(60)));
|
|
158
|
+
console.log(` ${events.length} events in ${formatTime(totalDuration)}${cumulativeCost > 0 ? ` | $${cumulativeCost.toFixed(4)} total cost` : ''}`);
|
|
159
|
+
console.log('');
|
|
160
|
+
}
|
|
161
|
+
function formatTime(ms) {
|
|
162
|
+
if (ms >= 60000)
|
|
163
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
164
|
+
if (ms >= 1000)
|
|
165
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
166
|
+
return `${ms}ms`;
|
|
167
|
+
}
|
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 playback
|
|
924
|
+
program
|
|
925
|
+
.command("playback")
|
|
926
|
+
.description("Replay agent execution step-by-step — chronological timeline of all decisions")
|
|
927
|
+
.option("--json", "Output structured JSON")
|
|
928
|
+
.action(async (opts) => {
|
|
929
|
+
const { playbackCommand } = await Promise.resolve().then(() => __importStar(require("./commands/playback")));
|
|
930
|
+
playbackCommand(opts);
|
|
931
|
+
});
|
|
923
932
|
// trickle summarize
|
|
924
933
|
program
|
|
925
934
|
.command("summarize")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trickle-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.202",
|
|
4
4
|
"description": "Zero-code runtime observability for JS/Python + AI agent debugging. Traces LangChain, CrewAI, OpenAI, Anthropic, Gemini. Eval, security, compliance, cost tracking. Free, local-first.",
|
|
5
5
|
"keywords": ["observability", "tracing", "llm", "openai", "anthropic", "langchain", "crewai", "agent", "mcp", "debugging", "typescript", "python", "security", "eval", "compliance"],
|
|
6
6
|
"bin": {
|
package/src/commands/demo.ts
CHANGED
|
@@ -65,7 +65,7 @@ export async function runDemo(): Promise<void> {
|
|
|
65
65
|
console.log('');
|
|
66
66
|
console.log(chalk.bold.cyan(' ╔══════════════════════════════════════════╗'));
|
|
67
67
|
console.log(chalk.bold.cyan(' ║ trickle — Live Demo ║'));
|
|
68
|
-
console.log(chalk.bold.cyan(' ║
|
|
68
|
+
console.log(chalk.bold.cyan(' ║ Zero-Code Observability in 60 Seconds ║'));
|
|
69
69
|
console.log(chalk.bold.cyan(' ╚══════════════════════════════════════════╝'));
|
|
70
70
|
|
|
71
71
|
// Step 1: Create demo project
|
|
@@ -175,9 +175,15 @@ export async function runDemo(): Promise<void> {
|
|
|
175
175
|
console.log(chalk.bold.cyan(' ║ ║'));
|
|
176
176
|
console.log(chalk.bold.cyan(' ║ npm install -g trickle-cli ║'));
|
|
177
177
|
console.log(chalk.bold.cyan(' ║ trickle init ║'));
|
|
178
|
-
console.log(chalk.bold.cyan(' ║ trickle run
|
|
178
|
+
console.log(chalk.bold.cyan(' ║ trickle run node app.js ║'));
|
|
179
179
|
console.log(chalk.bold.cyan(' ║ ║'));
|
|
180
|
-
console.log(chalk.bold.cyan(' ║
|
|
180
|
+
console.log(chalk.bold.cyan(' ║ Also try: ║'));
|
|
181
|
+
console.log(chalk.bold.cyan(' ║ trickle eval — A-F grading ║'));
|
|
182
|
+
console.log(chalk.bold.cyan(' ║ trickle why — root cause ║'));
|
|
183
|
+
console.log(chalk.bold.cyan(' ║ trickle cost-report — LLM costs ║'));
|
|
184
|
+
console.log(chalk.bold.cyan(' ║ trickle summarize — trace summary ║'));
|
|
185
|
+
console.log(chalk.bold.cyan(' ║ ║'));
|
|
186
|
+
console.log(chalk.bold.cyan(' ║ 38 MCP tools | Free, local, zero-code ║'));
|
|
181
187
|
console.log(chalk.bold.cyan(' ║ github.com/yiheinchai/trickle ║'));
|
|
182
188
|
console.log(chalk.bold.cyan(' ╚══════════════════════════════════════════╝'));
|
|
183
189
|
console.log('');
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trickle playback — Step-by-step replay of agent execution.
|
|
3
|
+
*
|
|
4
|
+
* Reads agents.jsonl, llm.jsonl, mcp.jsonl and plays back events
|
|
5
|
+
* in chronological order with timing. Local-first: instant replay
|
|
6
|
+
* from local JSONL files, no server needed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
|
|
13
|
+
function readJsonl(fp: string): any[] {
|
|
14
|
+
if (!fs.existsSync(fp)) return [];
|
|
15
|
+
return fs.readFileSync(fp, 'utf-8').split('\n').filter(Boolean)
|
|
16
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface PlaybackEvent {
|
|
20
|
+
timestamp: number;
|
|
21
|
+
source: string;
|
|
22
|
+
icon: string;
|
|
23
|
+
title: string;
|
|
24
|
+
detail: string;
|
|
25
|
+
durationMs?: number;
|
|
26
|
+
cost?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function playbackCommand(opts: { json?: boolean; speed?: string }): void {
|
|
30
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
31
|
+
const events: PlaybackEvent[] = [];
|
|
32
|
+
|
|
33
|
+
// Collect events from all sources
|
|
34
|
+
for (const e of readJsonl(path.join(dir, 'agents.jsonl'))) {
|
|
35
|
+
const evt = e.event || '?';
|
|
36
|
+
const name = e.chain || e.tool || '';
|
|
37
|
+
let icon = '→';
|
|
38
|
+
if (evt.includes('start')) icon = '▶';
|
|
39
|
+
if (evt.includes('end')) icon = '■';
|
|
40
|
+
if (evt.includes('error')) icon = '✗';
|
|
41
|
+
if (evt === 'action') icon = '⚒';
|
|
42
|
+
if (evt === 'finish') icon = '✔';
|
|
43
|
+
|
|
44
|
+
let detail = '';
|
|
45
|
+
if (e.toolInput) detail = `→ ${String(e.toolInput).substring(0, 80)}`;
|
|
46
|
+
if (e.output) detail = `← ${String(e.output).substring(0, 80)}`;
|
|
47
|
+
if (e.thought) detail = `💭 ${String(e.thought).substring(0, 80)}`;
|
|
48
|
+
if (e.error) detail = `✗ ${String(e.error).substring(0, 80)}`;
|
|
49
|
+
|
|
50
|
+
events.push({
|
|
51
|
+
timestamp: e.timestamp || 0,
|
|
52
|
+
source: `agent:${evt}`,
|
|
53
|
+
icon,
|
|
54
|
+
title: `${name} [${evt}]`,
|
|
55
|
+
detail,
|
|
56
|
+
durationMs: e.durationMs,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const c of readJsonl(path.join(dir, 'llm.jsonl'))) {
|
|
61
|
+
const err = c.error ? ` ERR: ${String(c.error).substring(0, 40)}` : '';
|
|
62
|
+
events.push({
|
|
63
|
+
timestamp: c.timestamp || 0,
|
|
64
|
+
source: 'llm',
|
|
65
|
+
icon: '✦',
|
|
66
|
+
title: `${c.provider}/${c.model}`,
|
|
67
|
+
detail: `${c.totalTokens || 0}tok ${c.estimatedCostUsd ? '$' + c.estimatedCostUsd.toFixed(4) : ''} → ${(c.inputPreview || '').substring(0, 50)}${err}`,
|
|
68
|
+
durationMs: c.durationMs,
|
|
69
|
+
cost: c.estimatedCostUsd,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const m of readJsonl(path.join(dir, 'mcp.jsonl'))) {
|
|
74
|
+
if (m.tool === '__list_tools') continue;
|
|
75
|
+
const dir2 = m.direction === 'outgoing' ? '→' : '←';
|
|
76
|
+
events.push({
|
|
77
|
+
timestamp: m.timestamp || 0,
|
|
78
|
+
source: 'mcp',
|
|
79
|
+
icon: dir2,
|
|
80
|
+
title: `MCP: ${m.tool}`,
|
|
81
|
+
detail: m.resultPreview ? `← ${String(m.resultPreview).substring(0, 60)}` : '',
|
|
82
|
+
durationMs: m.durationMs,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (events.length === 0) {
|
|
87
|
+
console.log(chalk.yellow(' No agent/LLM/MCP events to replay.'));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Sort chronologically
|
|
92
|
+
events.sort((a, b) => a.timestamp - b.timestamp);
|
|
93
|
+
|
|
94
|
+
if (opts.json) {
|
|
95
|
+
console.log(JSON.stringify(events, null, 2));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Playback
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log(chalk.bold(' trickle playback'));
|
|
102
|
+
console.log(chalk.gray(' ' + '─'.repeat(60)));
|
|
103
|
+
console.log(chalk.gray(` ${events.length} events | replaying chronologically`));
|
|
104
|
+
console.log('');
|
|
105
|
+
|
|
106
|
+
const startTs = events[0].timestamp;
|
|
107
|
+
let cumulativeCost = 0;
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < events.length; i++) {
|
|
110
|
+
const e = events[i];
|
|
111
|
+
const elapsed = e.timestamp - startTs;
|
|
112
|
+
const timeStr = formatTime(elapsed);
|
|
113
|
+
const durStr = e.durationMs ? chalk.gray(` (${e.durationMs.toFixed(0)}ms)`) : '';
|
|
114
|
+
if (e.cost) cumulativeCost += e.cost;
|
|
115
|
+
|
|
116
|
+
const iconColor = e.source === 'llm' ? chalk.magenta : e.source === 'mcp' ? chalk.green :
|
|
117
|
+
e.icon === '✗' ? chalk.red : e.icon === '✔' ? chalk.green : chalk.blue;
|
|
118
|
+
|
|
119
|
+
console.log(` ${chalk.gray(timeStr)} ${iconColor(e.icon)} ${chalk.bold(e.title)}${durStr}`);
|
|
120
|
+
if (e.detail) {
|
|
121
|
+
console.log(` ${chalk.gray(' ')} ${chalk.gray(e.detail)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Summary
|
|
126
|
+
const totalDuration = events[events.length - 1].timestamp - startTs;
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log(chalk.gray(' ' + '─'.repeat(60)));
|
|
129
|
+
console.log(` ${events.length} events in ${formatTime(totalDuration)}${cumulativeCost > 0 ? ` | $${cumulativeCost.toFixed(4)} total cost` : ''}`);
|
|
130
|
+
console.log('');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatTime(ms: number): string {
|
|
134
|
+
if (ms >= 60000) return `${(ms / 60000).toFixed(1)}m`;
|
|
135
|
+
if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;
|
|
136
|
+
return `${ms}ms`;
|
|
137
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -953,6 +953,16 @@ program
|
|
|
953
953
|
whyCommand(query, opts);
|
|
954
954
|
});
|
|
955
955
|
|
|
956
|
+
// trickle playback
|
|
957
|
+
program
|
|
958
|
+
.command("playback")
|
|
959
|
+
.description("Replay agent execution step-by-step — chronological timeline of all decisions")
|
|
960
|
+
.option("--json", "Output structured JSON")
|
|
961
|
+
.action(async (opts) => {
|
|
962
|
+
const { playbackCommand } = await import("./commands/playback");
|
|
963
|
+
playbackCommand(opts);
|
|
964
|
+
});
|
|
965
|
+
|
|
956
966
|
// trickle summarize
|
|
957
967
|
program
|
|
958
968
|
.command("summarize")
|