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.
@@ -12,5 +12,6 @@
12
12
  */
13
13
  export interface HintsOptions {
14
14
  values?: boolean;
15
+ errors?: boolean;
15
16
  }
16
17
  export declare function hintsCommand(targetFile: string | undefined, opts: HintsOptions): Promise<void>;
@@ -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 and deduplicate variable observations (last wins per file:line:varName)
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 !== "variable")
146
- continue;
147
- const key = `${v.file}:${v.line}:${v.varName}`;
148
- obsMap.set(key, v);
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
- const vars = [...obsMap.values()];
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
- console.log(`# ${relPath}`);
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
- if (opts.values && v.sample !== undefined) {
241
- console.log(`${v.varName}: ${typeStr} = ${formatSample(v.sample)}${scope}`);
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
- console.log(`# ${relPath}`);
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.215",
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",
@@ -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 and deduplicate variable observations (last wins per file:line:varName)
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 !== "variable") continue;
133
- const key = `${v.file}:${v.line}:${v.varName}`;
134
- obsMap.set(key, v);
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
- const vars = [...obsMap.values()];
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
- console.log(`# ${relPath}`);
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
- if (opts.values && v.sample !== undefined) {
227
- console.log(`${v.varName}: ${typeStr} = ${formatSample(v.sample)}${scope}`);
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
- console.log(`# ${relPath}`);
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
- .action(async (file: string | undefined, opts: { values?: boolean }) => {
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