trickle-cli 0.1.193 → 0.1.195
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/cleanup.d.ts +12 -0
- package/dist/commands/cleanup.js +173 -0
- package/dist/index.js +12 -0
- package/package.json +1 -1
- package/src/commands/cleanup.ts +162 -0
- package/src/index.ts +13 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trickle cleanup — Smart data management for .trickle/ files.
|
|
3
|
+
*
|
|
4
|
+
* Prunes old data, compacts JSONL files, and manages retention.
|
|
5
|
+
* Essential for heavy workloads where agents produce 10-100x more data.
|
|
6
|
+
*/
|
|
7
|
+
export declare function cleanupCommand(opts: {
|
|
8
|
+
retainDays?: string;
|
|
9
|
+
retainLines?: string;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
}): void;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* trickle cleanup — Smart data management for .trickle/ files.
|
|
4
|
+
*
|
|
5
|
+
* Prunes old data, compacts JSONL files, and manages retention.
|
|
6
|
+
* Essential for heavy workloads where agents produce 10-100x more data.
|
|
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.cleanupCommand = cleanupCommand;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
49
|
+
function cleanupCommand(opts) {
|
|
50
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
51
|
+
if (!fs.existsSync(dir)) {
|
|
52
|
+
console.log(chalk_1.default.yellow(' No .trickle/ directory found.'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const retainDays = opts.retainDays ? parseInt(opts.retainDays, 10) : 7;
|
|
56
|
+
const retainLines = opts.retainLines ? parseInt(opts.retainLines, 10) : 0;
|
|
57
|
+
const cutoffMs = Date.now() - retainDays * 24 * 60 * 60 * 1000;
|
|
58
|
+
const dryRun = opts.dryRun || false;
|
|
59
|
+
const jsonlFiles = [
|
|
60
|
+
'observations.jsonl', 'variables.jsonl', 'calltrace.jsonl',
|
|
61
|
+
'queries.jsonl', 'errors.jsonl', 'llm.jsonl', 'agents.jsonl',
|
|
62
|
+
'mcp.jsonl', 'logs.jsonl', 'console.jsonl', 'traces.jsonl',
|
|
63
|
+
'alerts.jsonl', 'profile.jsonl',
|
|
64
|
+
];
|
|
65
|
+
let totalBefore = 0;
|
|
66
|
+
let totalAfter = 0;
|
|
67
|
+
let totalLinesRemoved = 0;
|
|
68
|
+
let filesProcessed = 0;
|
|
69
|
+
const details = [];
|
|
70
|
+
for (const file of jsonlFiles) {
|
|
71
|
+
const filePath = path.join(dir, file);
|
|
72
|
+
if (!fs.existsSync(filePath))
|
|
73
|
+
continue;
|
|
74
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
75
|
+
const beforeSize = Buffer.byteLength(content);
|
|
76
|
+
totalBefore += beforeSize;
|
|
77
|
+
const lines = content.split('\n').filter(Boolean);
|
|
78
|
+
let kept;
|
|
79
|
+
if (retainLines > 0) {
|
|
80
|
+
// Keep only the last N lines
|
|
81
|
+
kept = lines.slice(-retainLines);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Keep lines with timestamp newer than cutoff
|
|
85
|
+
kept = lines.filter(line => {
|
|
86
|
+
try {
|
|
87
|
+
const obj = JSON.parse(line);
|
|
88
|
+
const ts = obj.timestamp || 0;
|
|
89
|
+
// If timestamp is in seconds (< 2000000000), convert to ms
|
|
90
|
+
const tsMs = ts < 2_000_000_000 ? ts * 1000 : ts;
|
|
91
|
+
// Keep if no timestamp (can't determine age) or if newer than cutoff
|
|
92
|
+
return !ts || tsMs > cutoffMs;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return true; // Keep unparseable lines
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const removed = lines.length - kept.length;
|
|
100
|
+
totalLinesRemoved += removed;
|
|
101
|
+
const newContent = kept.length > 0 ? kept.join('\n') + '\n' : '';
|
|
102
|
+
const afterSize = Buffer.byteLength(newContent);
|
|
103
|
+
totalAfter += afterSize;
|
|
104
|
+
filesProcessed++;
|
|
105
|
+
details.push({ file, before: beforeSize, after: afterSize, removed });
|
|
106
|
+
if (!dryRun && removed > 0) {
|
|
107
|
+
fs.writeFileSync(filePath, newContent, 'utf-8');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Also clean up snapshot directory if it exists and is old
|
|
111
|
+
const snapshotDir = path.join(dir, 'snapshot');
|
|
112
|
+
if (fs.existsSync(snapshotDir)) {
|
|
113
|
+
try {
|
|
114
|
+
const stat = fs.statSync(snapshotDir);
|
|
115
|
+
if (stat.mtimeMs < cutoffMs && !dryRun) {
|
|
116
|
+
fs.rmSync(snapshotDir, { recursive: true });
|
|
117
|
+
details.push({ file: 'snapshot/', before: 0, after: 0, removed: -1 });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
121
|
+
}
|
|
122
|
+
// Clean up CSV export directory
|
|
123
|
+
const csvDir = path.join(dir, 'csv');
|
|
124
|
+
if (fs.existsSync(csvDir) && !dryRun) {
|
|
125
|
+
try {
|
|
126
|
+
const stat = fs.statSync(csvDir);
|
|
127
|
+
if (stat.mtimeMs < cutoffMs) {
|
|
128
|
+
fs.rmSync(csvDir, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch { }
|
|
132
|
+
}
|
|
133
|
+
const result = {
|
|
134
|
+
filesProcessed,
|
|
135
|
+
linesRemoved: totalLinesRemoved,
|
|
136
|
+
bytesBefore: totalBefore,
|
|
137
|
+
bytesAfter: totalAfter,
|
|
138
|
+
bytesSaved: totalBefore - totalAfter,
|
|
139
|
+
};
|
|
140
|
+
if (opts.json) {
|
|
141
|
+
console.log(JSON.stringify({ ...result, details, dryRun, retainDays }, null, 2));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
console.log('');
|
|
145
|
+
console.log(chalk_1.default.bold(` trickle cleanup${dryRun ? ' (dry run)' : ''}`));
|
|
146
|
+
console.log(chalk_1.default.gray(' ' + '─'.repeat(50)));
|
|
147
|
+
console.log(` Retention: ${retainLines > 0 ? `last ${retainLines} lines per file` : `${retainDays} days`}`);
|
|
148
|
+
console.log(` Files scanned: ${filesProcessed}`);
|
|
149
|
+
if (totalLinesRemoved === 0) {
|
|
150
|
+
console.log(chalk_1.default.green(' No data to prune — all data is within retention window.'));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(` Lines removed: ${chalk_1.default.yellow(String(totalLinesRemoved))}`);
|
|
154
|
+
console.log(` Space: ${formatBytes(totalBefore)} → ${formatBytes(totalAfter)} (${chalk_1.default.green('saved ' + formatBytes(result.bytesSaved))})`);
|
|
155
|
+
if (dryRun) {
|
|
156
|
+
console.log(chalk_1.default.yellow('\n Dry run — no files modified. Remove --dry-run to apply.'));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
for (const d of details.filter(d => d.removed > 0)) {
|
|
160
|
+
console.log(chalk_1.default.gray(` ${d.file}: ${d.removed} lines removed`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
console.log(chalk_1.default.gray(' ' + '─'.repeat(50)));
|
|
165
|
+
console.log('');
|
|
166
|
+
}
|
|
167
|
+
function formatBytes(bytes) {
|
|
168
|
+
if (bytes >= 1_000_000)
|
|
169
|
+
return (bytes / 1_000_000).toFixed(1) + 'MB';
|
|
170
|
+
if (bytes >= 1_000)
|
|
171
|
+
return (bytes / 1_000).toFixed(1) + 'KB';
|
|
172
|
+
return bytes + 'B';
|
|
173
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -920,6 +920,18 @@ program
|
|
|
920
920
|
const { whyCommand } = await Promise.resolve().then(() => __importStar(require("./commands/why")));
|
|
921
921
|
whyCommand(query, opts);
|
|
922
922
|
});
|
|
923
|
+
// trickle cleanup
|
|
924
|
+
program
|
|
925
|
+
.command("cleanup")
|
|
926
|
+
.description("Prune old .trickle/ data — manage retention for heavy workloads")
|
|
927
|
+
.option("--retain-days <days>", "Keep data from last N days (default: 7)")
|
|
928
|
+
.option("--retain-lines <lines>", "Keep only last N lines per file (overrides --retain-days)")
|
|
929
|
+
.option("--dry-run", "Show what would be removed without modifying files")
|
|
930
|
+
.option("--json", "Output structured JSON")
|
|
931
|
+
.action(async (opts) => {
|
|
932
|
+
const { cleanupCommand } = await Promise.resolve().then(() => __importStar(require("./commands/cleanup")));
|
|
933
|
+
cleanupCommand(opts);
|
|
934
|
+
});
|
|
923
935
|
// trickle eval
|
|
924
936
|
program
|
|
925
937
|
.command("eval")
|
package/package.json
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trickle cleanup — Smart data management for .trickle/ files.
|
|
3
|
+
*
|
|
4
|
+
* Prunes old data, compacts JSONL files, and manages retention.
|
|
5
|
+
* Essential for heavy workloads where agents produce 10-100x more data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
|
|
12
|
+
interface CleanupResult {
|
|
13
|
+
filesProcessed: number;
|
|
14
|
+
linesRemoved: number;
|
|
15
|
+
bytesBefore: number;
|
|
16
|
+
bytesAfter: number;
|
|
17
|
+
bytesSaved: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function cleanupCommand(opts: {
|
|
21
|
+
retainDays?: string;
|
|
22
|
+
retainLines?: string;
|
|
23
|
+
dryRun?: boolean;
|
|
24
|
+
json?: boolean;
|
|
25
|
+
}): void {
|
|
26
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(dir)) {
|
|
29
|
+
console.log(chalk.yellow(' No .trickle/ directory found.'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const retainDays = opts.retainDays ? parseInt(opts.retainDays, 10) : 7;
|
|
34
|
+
const retainLines = opts.retainLines ? parseInt(opts.retainLines, 10) : 0;
|
|
35
|
+
const cutoffMs = Date.now() - retainDays * 24 * 60 * 60 * 1000;
|
|
36
|
+
const dryRun = opts.dryRun || false;
|
|
37
|
+
|
|
38
|
+
const jsonlFiles = [
|
|
39
|
+
'observations.jsonl', 'variables.jsonl', 'calltrace.jsonl',
|
|
40
|
+
'queries.jsonl', 'errors.jsonl', 'llm.jsonl', 'agents.jsonl',
|
|
41
|
+
'mcp.jsonl', 'logs.jsonl', 'console.jsonl', 'traces.jsonl',
|
|
42
|
+
'alerts.jsonl', 'profile.jsonl',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
let totalBefore = 0;
|
|
46
|
+
let totalAfter = 0;
|
|
47
|
+
let totalLinesRemoved = 0;
|
|
48
|
+
let filesProcessed = 0;
|
|
49
|
+
|
|
50
|
+
const details: Array<{ file: string; before: number; after: number; removed: number }> = [];
|
|
51
|
+
|
|
52
|
+
for (const file of jsonlFiles) {
|
|
53
|
+
const filePath = path.join(dir, file);
|
|
54
|
+
if (!fs.existsSync(filePath)) continue;
|
|
55
|
+
|
|
56
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
57
|
+
const beforeSize = Buffer.byteLength(content);
|
|
58
|
+
totalBefore += beforeSize;
|
|
59
|
+
|
|
60
|
+
const lines = content.split('\n').filter(Boolean);
|
|
61
|
+
let kept: string[];
|
|
62
|
+
|
|
63
|
+
if (retainLines > 0) {
|
|
64
|
+
// Keep only the last N lines
|
|
65
|
+
kept = lines.slice(-retainLines);
|
|
66
|
+
} else {
|
|
67
|
+
// Keep lines with timestamp newer than cutoff
|
|
68
|
+
kept = lines.filter(line => {
|
|
69
|
+
try {
|
|
70
|
+
const obj = JSON.parse(line);
|
|
71
|
+
const ts = obj.timestamp || 0;
|
|
72
|
+
// If timestamp is in seconds (< 2000000000), convert to ms
|
|
73
|
+
const tsMs = ts < 2_000_000_000 ? ts * 1000 : ts;
|
|
74
|
+
// Keep if no timestamp (can't determine age) or if newer than cutoff
|
|
75
|
+
return !ts || tsMs > cutoffMs;
|
|
76
|
+
} catch {
|
|
77
|
+
return true; // Keep unparseable lines
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const removed = lines.length - kept.length;
|
|
83
|
+
totalLinesRemoved += removed;
|
|
84
|
+
|
|
85
|
+
const newContent = kept.length > 0 ? kept.join('\n') + '\n' : '';
|
|
86
|
+
const afterSize = Buffer.byteLength(newContent);
|
|
87
|
+
totalAfter += afterSize;
|
|
88
|
+
filesProcessed++;
|
|
89
|
+
|
|
90
|
+
details.push({ file, before: beforeSize, after: afterSize, removed });
|
|
91
|
+
|
|
92
|
+
if (!dryRun && removed > 0) {
|
|
93
|
+
fs.writeFileSync(filePath, newContent, 'utf-8');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Also clean up snapshot directory if it exists and is old
|
|
98
|
+
const snapshotDir = path.join(dir, 'snapshot');
|
|
99
|
+
if (fs.existsSync(snapshotDir)) {
|
|
100
|
+
try {
|
|
101
|
+
const stat = fs.statSync(snapshotDir);
|
|
102
|
+
if (stat.mtimeMs < cutoffMs && !dryRun) {
|
|
103
|
+
fs.rmSync(snapshotDir, { recursive: true });
|
|
104
|
+
details.push({ file: 'snapshot/', before: 0, after: 0, removed: -1 });
|
|
105
|
+
}
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Clean up CSV export directory
|
|
110
|
+
const csvDir = path.join(dir, 'csv');
|
|
111
|
+
if (fs.existsSync(csvDir) && !dryRun) {
|
|
112
|
+
try {
|
|
113
|
+
const stat = fs.statSync(csvDir);
|
|
114
|
+
if (stat.mtimeMs < cutoffMs) {
|
|
115
|
+
fs.rmSync(csvDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result: CleanupResult = {
|
|
121
|
+
filesProcessed,
|
|
122
|
+
linesRemoved: totalLinesRemoved,
|
|
123
|
+
bytesBefore: totalBefore,
|
|
124
|
+
bytesAfter: totalAfter,
|
|
125
|
+
bytesSaved: totalBefore - totalAfter,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (opts.json) {
|
|
129
|
+
console.log(JSON.stringify({ ...result, details, dryRun, retainDays }, null, 2));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(chalk.bold(` trickle cleanup${dryRun ? ' (dry run)' : ''}`));
|
|
135
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)));
|
|
136
|
+
console.log(` Retention: ${retainLines > 0 ? `last ${retainLines} lines per file` : `${retainDays} days`}`);
|
|
137
|
+
console.log(` Files scanned: ${filesProcessed}`);
|
|
138
|
+
|
|
139
|
+
if (totalLinesRemoved === 0) {
|
|
140
|
+
console.log(chalk.green(' No data to prune — all data is within retention window.'));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(` Lines removed: ${chalk.yellow(String(totalLinesRemoved))}`);
|
|
143
|
+
console.log(` Space: ${formatBytes(totalBefore)} → ${formatBytes(totalAfter)} (${chalk.green('saved ' + formatBytes(result.bytesSaved))})`);
|
|
144
|
+
|
|
145
|
+
if (dryRun) {
|
|
146
|
+
console.log(chalk.yellow('\n Dry run — no files modified. Remove --dry-run to apply.'));
|
|
147
|
+
} else {
|
|
148
|
+
for (const d of details.filter(d => d.removed > 0)) {
|
|
149
|
+
console.log(chalk.gray(` ${d.file}: ${d.removed} lines removed`));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(chalk.gray(' ' + '─'.repeat(50)));
|
|
155
|
+
console.log('');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function formatBytes(bytes: number): string {
|
|
159
|
+
if (bytes >= 1_000_000) return (bytes / 1_000_000).toFixed(1) + 'MB';
|
|
160
|
+
if (bytes >= 1_000) return (bytes / 1_000).toFixed(1) + 'KB';
|
|
161
|
+
return bytes + 'B';
|
|
162
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -953,6 +953,19 @@ program
|
|
|
953
953
|
whyCommand(query, opts);
|
|
954
954
|
});
|
|
955
955
|
|
|
956
|
+
// trickle cleanup
|
|
957
|
+
program
|
|
958
|
+
.command("cleanup")
|
|
959
|
+
.description("Prune old .trickle/ data — manage retention for heavy workloads")
|
|
960
|
+
.option("--retain-days <days>", "Keep data from last N days (default: 7)")
|
|
961
|
+
.option("--retain-lines <lines>", "Keep only last N lines per file (overrides --retain-days)")
|
|
962
|
+
.option("--dry-run", "Show what would be removed without modifying files")
|
|
963
|
+
.option("--json", "Output structured JSON")
|
|
964
|
+
.action(async (opts) => {
|
|
965
|
+
const { cleanupCommand } = await import("./commands/cleanup");
|
|
966
|
+
cleanupCommand(opts);
|
|
967
|
+
});
|
|
968
|
+
|
|
956
969
|
// trickle eval
|
|
957
970
|
program
|
|
958
971
|
.command("eval")
|