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