trickle-cli 0.1.215 → 0.1.218
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/hints.d.ts +1 -0
- package/dist/commands/hints.js +75 -12
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/commands/hints.ts +78 -11
- package/src/index.ts +2 -1
package/dist/commands/hints.d.ts
CHANGED
package/dist/commands/hints.js
CHANGED
|
@@ -137,19 +137,35 @@ async function hintsCommand(targetFile, opts) {
|
|
|
137
137
|
console.error(" trickle run node app.js");
|
|
138
138
|
process.exit(1);
|
|
139
139
|
}
|
|
140
|
-
// Load
|
|
140
|
+
// Load observations from variables.jsonl
|
|
141
141
|
const obsMap = new Map();
|
|
142
|
+
const errorSnaps = [];
|
|
143
|
+
const targetKinds = opts.errors ? ["error_snapshot"] : ["variable"];
|
|
142
144
|
for (const line of fs.readFileSync(varsFile, "utf-8").split("\n").filter(Boolean)) {
|
|
143
145
|
try {
|
|
144
146
|
const v = JSON.parse(line);
|
|
145
|
-
if (v.kind
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
if (v.kind === "variable") {
|
|
148
|
+
const key = `${v.file}:${v.line}:${v.varName}`;
|
|
149
|
+
obsMap.set(key, v);
|
|
150
|
+
}
|
|
151
|
+
if (v.kind === "error_snapshot") {
|
|
152
|
+
errorSnaps.push(v);
|
|
153
|
+
}
|
|
149
154
|
}
|
|
150
155
|
catch { }
|
|
151
156
|
}
|
|
152
|
-
|
|
157
|
+
// In error mode, use error snapshots; look up original assignment lines from regular vars
|
|
158
|
+
let vars;
|
|
159
|
+
if (opts.errors) {
|
|
160
|
+
if (errorSnaps.length === 0) {
|
|
161
|
+
console.error("No error snapshots found. Run code that produces an error first.");
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
vars = errorSnaps;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
vars = [...obsMap.values()];
|
|
168
|
+
}
|
|
153
169
|
// Filter by target file
|
|
154
170
|
let filtered = vars;
|
|
155
171
|
if (targetFile) {
|
|
@@ -163,6 +179,19 @@ async function hintsCommand(targetFile, opts) {
|
|
|
163
179
|
console.error(targetFile ? `No observations found for "${targetFile}".` : "No observations found.");
|
|
164
180
|
process.exit(1);
|
|
165
181
|
}
|
|
182
|
+
// In error mode, resolve each snapshot var to its original assignment line
|
|
183
|
+
// by looking up the regular variable observations
|
|
184
|
+
if (opts.errors) {
|
|
185
|
+
for (const snap of filtered) {
|
|
186
|
+
// Find matching regular observation to get original line
|
|
187
|
+
for (const [, obs] of obsMap) {
|
|
188
|
+
if (obs.file === snap.file && obs.varName === snap.varName) {
|
|
189
|
+
snap.line = obs.line; // use original assignment line
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
166
195
|
// Group by file
|
|
167
196
|
const byFile = new Map();
|
|
168
197
|
for (const v of filtered) {
|
|
@@ -231,14 +260,26 @@ async function hintsCommand(targetFile, opts) {
|
|
|
231
260
|
lineObs.set(v.line, new Map());
|
|
232
261
|
lineObs.get(v.line).set(v.varName, v);
|
|
233
262
|
}
|
|
234
|
-
|
|
263
|
+
// Print header with error info if in error mode
|
|
264
|
+
const errorMsg = fileVars.find(v => v.error)?.error;
|
|
265
|
+
const errorLine = fileVars.find(v => v.errorLine)?.errorLine;
|
|
266
|
+
if (opts.errors && errorMsg) {
|
|
267
|
+
console.log(`# ${relPath} — ERROR`);
|
|
268
|
+
console.log(`# ${errorMsg}${errorLine ? ` (line ${errorLine})` : ""}`);
|
|
269
|
+
console.log(`# Variables at crash time:`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
console.log(`# ${relPath}`);
|
|
273
|
+
}
|
|
235
274
|
console.log("```python");
|
|
236
275
|
for (const [lineNo, obs] of [...lineObs.entries()].sort((a, b) => a[0] - b[0])) {
|
|
237
276
|
for (const v of obs.values()) {
|
|
238
277
|
const typeStr = typeToString(v.type);
|
|
239
278
|
const scope = v.funcName ? ` (in ${v.funcName})` : "";
|
|
240
|
-
|
|
241
|
-
|
|
279
|
+
const sampleStr = formatSample(v.sample);
|
|
280
|
+
// In error mode, always show values (crash-time state)
|
|
281
|
+
if ((opts.errors || opts.values) && v.sample !== undefined) {
|
|
282
|
+
console.log(`${v.varName}: ${typeStr} = ${sampleStr}${scope}`);
|
|
242
283
|
}
|
|
243
284
|
else {
|
|
244
285
|
console.log(`${v.varName}: ${typeStr}${scope}`);
|
|
@@ -263,7 +304,17 @@ async function hintsCommand(targetFile, opts) {
|
|
|
263
304
|
lineObs.set(v.line, new Map());
|
|
264
305
|
lineObs.get(v.line).set(v.varName, v);
|
|
265
306
|
}
|
|
266
|
-
|
|
307
|
+
// Print header with error info if in error mode
|
|
308
|
+
const errMsg = fileVars.find(v => v.error)?.error;
|
|
309
|
+
const errLine = fileVars.find(v => v.errorLine)?.errorLine;
|
|
310
|
+
if (opts.errors && errMsg) {
|
|
311
|
+
console.log(`# ${relPath} — ERROR`);
|
|
312
|
+
console.log(`# ${errMsg}${errLine ? ` (line ${errLine})` : ""}`);
|
|
313
|
+
console.log(`# Variables at crash time:`);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
console.log(`# ${relPath}`);
|
|
317
|
+
}
|
|
267
318
|
console.log("```python");
|
|
268
319
|
for (let i = 0; i < sourceLines.length; i++) {
|
|
269
320
|
const lineNo = i + 1;
|
|
@@ -271,6 +322,12 @@ async function hintsCommand(targetFile, opts) {
|
|
|
271
322
|
const obs = lineObs.get(lineNo);
|
|
272
323
|
if (!obs || obs.size === 0) {
|
|
273
324
|
console.log(src);
|
|
325
|
+
// Show error underline on the error line
|
|
326
|
+
if (opts.errors && errLine && lineNo === errLine) {
|
|
327
|
+
const indent = src.match(/^(\s*)/)?.[1] || "";
|
|
328
|
+
const contentLen = src.trimEnd().length - indent.length;
|
|
329
|
+
console.log(indent + "~".repeat(Math.max(contentLen, 1)) + ` ← ${errMsg}`);
|
|
330
|
+
}
|
|
274
331
|
continue;
|
|
275
332
|
}
|
|
276
333
|
// Insert type hints inline after variable names
|
|
@@ -314,9 +371,9 @@ async function hintsCommand(targetFile, opts) {
|
|
|
314
371
|
if (isFuncParam && afterVar.startsWith(":"))
|
|
315
372
|
continue;
|
|
316
373
|
}
|
|
317
|
-
// Build the hint string
|
|
374
|
+
// Build the hint string — error mode always shows values (crash-time state)
|
|
318
375
|
let hint;
|
|
319
|
-
if (opts.values && v.sample !== undefined && v.sample !== null) {
|
|
376
|
+
if ((opts.errors || opts.values) && v.sample !== undefined && v.sample !== null) {
|
|
320
377
|
const sampleStr = formatSample(v.sample);
|
|
321
378
|
hint = sampleStr ? `: ${typeStr} = ${sampleStr}` : `: ${typeStr}`;
|
|
322
379
|
}
|
|
@@ -327,6 +384,12 @@ async function hintsCommand(targetFile, opts) {
|
|
|
327
384
|
annotated = annotated.substring(0, varEnd) + hint + annotated.substring(varEnd);
|
|
328
385
|
}
|
|
329
386
|
console.log(annotated);
|
|
387
|
+
// Show error underline on the error line
|
|
388
|
+
if (opts.errors && errLine && lineNo === errLine) {
|
|
389
|
+
const indent = src.match(/^(\s*)/)?.[1] || "";
|
|
390
|
+
const contentLen = src.trimEnd().length - indent.length;
|
|
391
|
+
console.log(indent + "~".repeat(Math.max(contentLen, 1)) + ` ← ${errMsg}`);
|
|
392
|
+
}
|
|
330
393
|
}
|
|
331
394
|
console.log("```");
|
|
332
395
|
console.log("");
|
package/dist/index.js
CHANGED
|
@@ -539,6 +539,7 @@ program
|
|
|
539
539
|
.command("hints [file]")
|
|
540
540
|
.description("Output source code with inline type hints from runtime observations (for AI agents)")
|
|
541
541
|
.option("--values", "Include sample values alongside types")
|
|
542
|
+
.option("--errors", "Show error mode — variables at crash time with values that caused the error")
|
|
542
543
|
.action(async (file, opts) => {
|
|
543
544
|
await (0, hints_1.hintsCommand)(file, opts);
|
|
544
545
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trickle-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.218",
|
|
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": [
|
|
6
6
|
"observability",
|
package/src/commands/hints.ts
CHANGED
|
@@ -16,6 +16,7 @@ import * as path from "path";
|
|
|
16
16
|
|
|
17
17
|
export interface HintsOptions {
|
|
18
18
|
values?: boolean;
|
|
19
|
+
errors?: boolean;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
interface TypeNode {
|
|
@@ -38,6 +39,8 @@ interface VarObservation {
|
|
|
38
39
|
type: TypeNode;
|
|
39
40
|
sample?: unknown;
|
|
40
41
|
funcName?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
errorLine?: number;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
function typeToString(node: TypeNode): string {
|
|
@@ -124,17 +127,35 @@ export async function hintsCommand(
|
|
|
124
127
|
process.exit(1);
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
// Load
|
|
130
|
+
// Load observations from variables.jsonl
|
|
128
131
|
const obsMap = new Map<string, VarObservation>();
|
|
132
|
+
const errorSnaps: VarObservation[] = [];
|
|
133
|
+
const targetKinds = opts.errors ? ["error_snapshot"] : ["variable"];
|
|
134
|
+
|
|
129
135
|
for (const line of fs.readFileSync(varsFile, "utf-8").split("\n").filter(Boolean)) {
|
|
130
136
|
try {
|
|
131
137
|
const v = JSON.parse(line) as VarObservation;
|
|
132
|
-
if (v.kind
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
if (v.kind === "variable") {
|
|
139
|
+
const key = `${v.file}:${v.line}:${v.varName}`;
|
|
140
|
+
obsMap.set(key, v);
|
|
141
|
+
}
|
|
142
|
+
if (v.kind === "error_snapshot") {
|
|
143
|
+
errorSnaps.push(v);
|
|
144
|
+
}
|
|
135
145
|
} catch {}
|
|
136
146
|
}
|
|
137
|
-
|
|
147
|
+
|
|
148
|
+
// In error mode, use error snapshots; look up original assignment lines from regular vars
|
|
149
|
+
let vars: VarObservation[];
|
|
150
|
+
if (opts.errors) {
|
|
151
|
+
if (errorSnaps.length === 0) {
|
|
152
|
+
console.error("No error snapshots found. Run code that produces an error first.");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
vars = errorSnaps;
|
|
156
|
+
} else {
|
|
157
|
+
vars = [...obsMap.values()];
|
|
158
|
+
}
|
|
138
159
|
|
|
139
160
|
// Filter by target file
|
|
140
161
|
let filtered = vars;
|
|
@@ -151,6 +172,20 @@ export async function hintsCommand(
|
|
|
151
172
|
process.exit(1);
|
|
152
173
|
}
|
|
153
174
|
|
|
175
|
+
// In error mode, resolve each snapshot var to its original assignment line
|
|
176
|
+
// by looking up the regular variable observations
|
|
177
|
+
if (opts.errors) {
|
|
178
|
+
for (const snap of filtered) {
|
|
179
|
+
// Find matching regular observation to get original line
|
|
180
|
+
for (const [, obs] of obsMap) {
|
|
181
|
+
if (obs.file === snap.file && obs.varName === snap.varName) {
|
|
182
|
+
snap.line = obs.line; // use original assignment line
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
154
189
|
// Group by file
|
|
155
190
|
const byFile = new Map<string, VarObservation[]>();
|
|
156
191
|
for (const v of filtered) {
|
|
@@ -217,14 +252,25 @@ export async function hintsCommand(
|
|
|
217
252
|
if (!lineObs.has(v.line)) lineObs.set(v.line, new Map());
|
|
218
253
|
lineObs.get(v.line)!.set(v.varName, v);
|
|
219
254
|
}
|
|
220
|
-
|
|
255
|
+
// Print header with error info if in error mode
|
|
256
|
+
const errorMsg = fileVars.find(v => v.error)?.error;
|
|
257
|
+
const errorLine = fileVars.find(v => v.errorLine)?.errorLine;
|
|
258
|
+
if (opts.errors && errorMsg) {
|
|
259
|
+
console.log(`# ${relPath} — ERROR`);
|
|
260
|
+
console.log(`# ${errorMsg}${errorLine ? ` (line ${errorLine})` : ""}`);
|
|
261
|
+
console.log(`# Variables at crash time:`);
|
|
262
|
+
} else {
|
|
263
|
+
console.log(`# ${relPath}`);
|
|
264
|
+
}
|
|
221
265
|
console.log("```python");
|
|
222
266
|
for (const [lineNo, obs] of [...lineObs.entries()].sort((a, b) => a[0] - b[0])) {
|
|
223
267
|
for (const v of obs.values()) {
|
|
224
268
|
const typeStr = typeToString(v.type);
|
|
225
269
|
const scope = v.funcName ? ` (in ${v.funcName})` : "";
|
|
226
|
-
|
|
227
|
-
|
|
270
|
+
const sampleStr = formatSample(v.sample);
|
|
271
|
+
// In error mode, always show values (crash-time state)
|
|
272
|
+
if ((opts.errors || opts.values) && v.sample !== undefined) {
|
|
273
|
+
console.log(`${v.varName}: ${typeStr} = ${sampleStr}${scope}`);
|
|
228
274
|
} else {
|
|
229
275
|
console.log(`${v.varName}: ${typeStr}${scope}`);
|
|
230
276
|
}
|
|
@@ -247,7 +293,16 @@ export async function hintsCommand(
|
|
|
247
293
|
lineObs.get(v.line)!.set(v.varName, v);
|
|
248
294
|
}
|
|
249
295
|
|
|
250
|
-
|
|
296
|
+
// Print header with error info if in error mode
|
|
297
|
+
const errMsg = fileVars.find(v => v.error)?.error;
|
|
298
|
+
const errLine = fileVars.find(v => v.errorLine)?.errorLine;
|
|
299
|
+
if (opts.errors && errMsg) {
|
|
300
|
+
console.log(`# ${relPath} — ERROR`);
|
|
301
|
+
console.log(`# ${errMsg}${errLine ? ` (line ${errLine})` : ""}`);
|
|
302
|
+
console.log(`# Variables at crash time:`);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(`# ${relPath}`);
|
|
305
|
+
}
|
|
251
306
|
console.log("```python");
|
|
252
307
|
|
|
253
308
|
for (let i = 0; i < sourceLines.length; i++) {
|
|
@@ -257,6 +312,12 @@ export async function hintsCommand(
|
|
|
257
312
|
|
|
258
313
|
if (!obs || obs.size === 0) {
|
|
259
314
|
console.log(src);
|
|
315
|
+
// Show error underline on the error line
|
|
316
|
+
if (opts.errors && errLine && lineNo === errLine) {
|
|
317
|
+
const indent = src.match(/^(\s*)/)?.[1] || "";
|
|
318
|
+
const contentLen = src.trimEnd().length - indent.length;
|
|
319
|
+
console.log(indent + "~".repeat(Math.max(contentLen, 1)) + ` ← ${errMsg}`);
|
|
320
|
+
}
|
|
260
321
|
continue;
|
|
261
322
|
}
|
|
262
323
|
|
|
@@ -302,9 +363,9 @@ export async function hintsCommand(
|
|
|
302
363
|
if (isFuncParam && afterVar.startsWith(":")) continue;
|
|
303
364
|
}
|
|
304
365
|
|
|
305
|
-
// Build the hint string
|
|
366
|
+
// Build the hint string — error mode always shows values (crash-time state)
|
|
306
367
|
let hint: string;
|
|
307
|
-
if (opts.values && v.sample !== undefined && v.sample !== null) {
|
|
368
|
+
if ((opts.errors || opts.values) && v.sample !== undefined && v.sample !== null) {
|
|
308
369
|
const sampleStr = formatSample(v.sample);
|
|
309
370
|
hint = sampleStr ? `: ${typeStr} = ${sampleStr}` : `: ${typeStr}`;
|
|
310
371
|
} else {
|
|
@@ -316,6 +377,12 @@ export async function hintsCommand(
|
|
|
316
377
|
}
|
|
317
378
|
|
|
318
379
|
console.log(annotated);
|
|
380
|
+
// Show error underline on the error line
|
|
381
|
+
if (opts.errors && errLine && lineNo === errLine) {
|
|
382
|
+
const indent = src.match(/^(\s*)/)?.[1] || "";
|
|
383
|
+
const contentLen = src.trimEnd().length - indent.length;
|
|
384
|
+
console.log(indent + "~".repeat(Math.max(contentLen, 1)) + ` ← ${errMsg}`);
|
|
385
|
+
}
|
|
319
386
|
}
|
|
320
387
|
|
|
321
388
|
console.log("```");
|
package/src/index.ts
CHANGED
|
@@ -540,7 +540,8 @@ program
|
|
|
540
540
|
.command("hints [file]")
|
|
541
541
|
.description("Output source code with inline type hints from runtime observations (for AI agents)")
|
|
542
542
|
.option("--values", "Include sample values alongside types")
|
|
543
|
-
.
|
|
543
|
+
.option("--errors", "Show error mode — variables at crash time with values that caused the error")
|
|
544
|
+
.action(async (file: string | undefined, opts: { values?: boolean; errors?: boolean }) => {
|
|
544
545
|
await hintsCommand(file, opts);
|
|
545
546
|
});
|
|
546
547
|
|