trickle-cli 0.1.3 → 0.1.5
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/api-client.js +1 -1
- package/dist/commands/init.js +146 -20
- package/dist/commands/run.js +47 -14
- package/dist/commands/vars.d.ts +12 -0
- package/dist/commands/vars.js +305 -0
- package/dist/index.js +12 -0
- package/package.json +1 -1
- package/src/api-client.ts +1 -1
- package/src/commands/init.ts +150 -18
- package/src/commands/run.ts +52 -14
- package/src/commands/vars.ts +321 -0
- package/src/index.ts +13 -0
package/dist/api-client.js
CHANGED
|
@@ -157,7 +157,7 @@ async function fetchFunctionSamples() {
|
|
|
157
157
|
const samples = [];
|
|
158
158
|
for (const fn of functions) {
|
|
159
159
|
try {
|
|
160
|
-
const data = await fetchJson(`/api/types/${
|
|
160
|
+
const data = await fetchJson(`/api/types/${fn.id}`);
|
|
161
161
|
if (data.snapshots && data.snapshots.length > 0) {
|
|
162
162
|
const snap = data.snapshots[0];
|
|
163
163
|
if (snap.sample_input || snap.sample_output) {
|
package/dist/commands/init.js
CHANGED
|
@@ -67,11 +67,13 @@ function detectProject(dir, forcePython) {
|
|
|
67
67
|
if (fs.existsSync(tsPath)) {
|
|
68
68
|
info.hasTsConfig = true;
|
|
69
69
|
try {
|
|
70
|
-
// Strip comments
|
|
70
|
+
// Strip comments but preserve glob patterns like /**/*.ts
|
|
71
71
|
const raw = fs.readFileSync(tsPath, "utf-8");
|
|
72
|
+
// Only strip // comments that are NOT inside strings
|
|
73
|
+
// And /* */ comments that are NOT inside strings (careful with globs)
|
|
72
74
|
const cleaned = raw
|
|
73
75
|
.replace(/\/\/.*$/gm, "")
|
|
74
|
-
.replace(
|
|
76
|
+
.replace(/("[^"]*")|\/\*[\s\S]*?\*\//g, (match, str) => str || "");
|
|
75
77
|
info.tsConfig = JSON.parse(cleaned);
|
|
76
78
|
}
|
|
77
79
|
catch {
|
|
@@ -205,7 +207,7 @@ function writeInitialTypes(trickleDir, isPython) {
|
|
|
205
207
|
}
|
|
206
208
|
function updateTsConfig(dir, info) {
|
|
207
209
|
const tsConfigPath = path.join(dir, "tsconfig.json");
|
|
208
|
-
if (!info.hasTsConfig
|
|
210
|
+
if (!info.hasTsConfig) {
|
|
209
211
|
// No tsconfig — create a minimal one that includes .trickle
|
|
210
212
|
if (!info.isPython) {
|
|
211
213
|
const newConfig = {
|
|
@@ -225,23 +227,52 @@ function updateTsConfig(dir, info) {
|
|
|
225
227
|
}
|
|
226
228
|
return false;
|
|
227
229
|
}
|
|
228
|
-
//
|
|
230
|
+
// Use text-based insertion to preserve formatting and glob patterns
|
|
231
|
+
// (JSON.parse + stringify corrupts /**/ globs by treating them as comments)
|
|
229
232
|
const raw = fs.readFileSync(tsConfigPath, "utf-8");
|
|
230
|
-
const config = info.tsConfig;
|
|
231
233
|
// Check if .trickle is already included
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
if (raw.includes(".trickle"))
|
|
235
|
+
return false;
|
|
236
|
+
// Find the "include" array and insert ".trickle" at the end
|
|
237
|
+
const includeMatch = raw.match(/"include"\s*:\s*\[([^\]]*)\]/);
|
|
238
|
+
if (includeMatch) {
|
|
239
|
+
const bracket = includeMatch.index + includeMatch[0].length - 1; // position of ]
|
|
240
|
+
const inside = includeMatch[1];
|
|
241
|
+
// Detect if it's single-line or multi-line
|
|
242
|
+
if (inside.includes("\n")) {
|
|
243
|
+
// Multi-line — add before the closing bracket with same indentation
|
|
244
|
+
const lastEntry = inside.match(/.*\S[^\n]*/g);
|
|
245
|
+
const indent = lastEntry ? lastEntry[lastEntry.length - 1].match(/^(\s*)/)?.[1] || " " : " ";
|
|
246
|
+
const updated = raw.slice(0, bracket) + `,\n${indent}".trickle"` + raw.slice(bracket);
|
|
247
|
+
fs.writeFileSync(tsConfigPath, updated, "utf-8");
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Single-line: ["src/**/*.ts", "tmp/**/*.ts"] → add ".trickle"
|
|
251
|
+
const updated = raw.slice(0, bracket) + ', ".trickle"' + raw.slice(bracket);
|
|
252
|
+
fs.writeFileSync(tsConfigPath, updated, "utf-8");
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
// No include array — add one after compilerOptions closing brace
|
|
257
|
+
const compilerEnd = raw.match(/"compilerOptions"\s*:\s*\{/);
|
|
258
|
+
if (compilerEnd) {
|
|
259
|
+
// Find the closing brace of compilerOptions
|
|
260
|
+
const startBrace = raw.indexOf("{", compilerEnd.index + compilerEnd[0].length - 1);
|
|
261
|
+
let depth = 1;
|
|
262
|
+
let pos = startBrace + 1;
|
|
263
|
+
while (pos < raw.length && depth > 0) {
|
|
264
|
+
if (raw[pos] === "{")
|
|
265
|
+
depth++;
|
|
266
|
+
else if (raw[pos] === "}")
|
|
267
|
+
depth--;
|
|
268
|
+
pos++;
|
|
269
|
+
}
|
|
270
|
+
// pos is now right after the closing brace of compilerOptions
|
|
271
|
+
const updated = raw.slice(0, pos) + `,\n "include": ["src", ".trickle"]` + raw.slice(pos);
|
|
272
|
+
fs.writeFileSync(tsConfigPath, updated, "utf-8");
|
|
273
|
+
return true;
|
|
242
274
|
}
|
|
243
|
-
|
|
244
|
-
return true;
|
|
275
|
+
return false;
|
|
245
276
|
}
|
|
246
277
|
function updatePackageJson(dir, info) {
|
|
247
278
|
if (!info.hasPackageJson || !info.packageJson) {
|
|
@@ -291,6 +322,93 @@ function updatePackageJson(dir, info) {
|
|
|
291
322
|
}
|
|
292
323
|
return { scriptsAdded: added };
|
|
293
324
|
}
|
|
325
|
+
function updateVitestConfig(dir) {
|
|
326
|
+
// Look for vitest.config.ts, vitest.config.js, vitest.config.mts, vite.config.ts, vite.config.js
|
|
327
|
+
const candidates = [
|
|
328
|
+
"vitest.config.ts",
|
|
329
|
+
"vitest.config.js",
|
|
330
|
+
"vitest.config.mts",
|
|
331
|
+
"vite.config.ts",
|
|
332
|
+
"vite.config.js",
|
|
333
|
+
];
|
|
334
|
+
let configFile = null;
|
|
335
|
+
for (const c of candidates) {
|
|
336
|
+
if (fs.existsSync(path.join(dir, c))) {
|
|
337
|
+
configFile = c;
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (!configFile)
|
|
342
|
+
return false;
|
|
343
|
+
const configPath = path.join(dir, configFile);
|
|
344
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
345
|
+
// Already has tricklePlugin
|
|
346
|
+
if (content.includes("tricklePlugin"))
|
|
347
|
+
return false;
|
|
348
|
+
// Find the import section end and plugins array
|
|
349
|
+
const lines = content.split("\n");
|
|
350
|
+
let lastImportLine = -1;
|
|
351
|
+
let pluginsLine = -1;
|
|
352
|
+
let pluginsArrayContent = "";
|
|
353
|
+
for (let i = 0; i < lines.length; i++) {
|
|
354
|
+
const line = lines[i];
|
|
355
|
+
if (/^\s*import\s/.test(line)) {
|
|
356
|
+
// Track multi-line imports
|
|
357
|
+
lastImportLine = i;
|
|
358
|
+
if (!line.includes(";") && !line.includes("from")) {
|
|
359
|
+
// Multi-line import — find the closing line
|
|
360
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
361
|
+
if (lines[j].includes("from")) {
|
|
362
|
+
lastImportLine = j;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (/plugins\s*:\s*\[/.test(line)) {
|
|
369
|
+
pluginsLine = i;
|
|
370
|
+
pluginsArrayContent = line;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (lastImportLine === -1)
|
|
374
|
+
return false;
|
|
375
|
+
// Add import after last import
|
|
376
|
+
const importLine = `import { tricklePlugin } from "trickle-observe/vite-plugin";`;
|
|
377
|
+
lines.splice(lastImportLine + 1, 0, importLine);
|
|
378
|
+
// Adjust pluginsLine index since we inserted a line
|
|
379
|
+
if (pluginsLine > lastImportLine) {
|
|
380
|
+
pluginsLine += 1;
|
|
381
|
+
}
|
|
382
|
+
// Add tricklePlugin() to plugins array
|
|
383
|
+
if (pluginsLine !== -1) {
|
|
384
|
+
const line = lines[pluginsLine];
|
|
385
|
+
// Check if plugins array is on one line: plugins: [something()],
|
|
386
|
+
if (/plugins\s*:\s*\[.*\]/.test(line)) {
|
|
387
|
+
// Insert tricklePlugin() before the closing bracket
|
|
388
|
+
lines[pluginsLine] = line.replace(/\]/, ", tricklePlugin()]");
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
// Multi-line plugins — add after the opening bracket line
|
|
392
|
+
const indent = line.match(/^(\s*)/)?.[1] || "";
|
|
393
|
+
const innerIndent = indent + " ";
|
|
394
|
+
lines.splice(pluginsLine + 1, 0, `${innerIndent}tricklePlugin(),`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
// No plugins array found — need to add one
|
|
399
|
+
// Find defineConfig({ and add plugins after it
|
|
400
|
+
for (let i = 0; i < lines.length; i++) {
|
|
401
|
+
if (/defineConfig\s*\(\s*\{/.test(lines[i])) {
|
|
402
|
+
const indent = lines[i].match(/^(\s*)/)?.[1] || "";
|
|
403
|
+
const innerIndent = indent + " ";
|
|
404
|
+
lines.splice(i + 1, 0, `${innerIndent}plugins: [tricklePlugin()],`);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
fs.writeFileSync(configPath, lines.join("\n"), "utf-8");
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
294
412
|
function updateGitignore(dir) {
|
|
295
413
|
const giPath = path.join(dir, ".gitignore");
|
|
296
414
|
let content = "";
|
|
@@ -359,7 +477,14 @@ async function initCommand(opts) {
|
|
|
359
477
|
console.log(` ${chalk_1.default.gray("-")} tsconfig.json already includes .trickle`);
|
|
360
478
|
}
|
|
361
479
|
}
|
|
362
|
-
// Step 5: Update
|
|
480
|
+
// Step 5: Update vitest/vite config with tricklePlugin
|
|
481
|
+
if (!info.isPython) {
|
|
482
|
+
const vitestUpdated = updateVitestConfig(dir);
|
|
483
|
+
if (vitestUpdated) {
|
|
484
|
+
console.log(` ${chalk_1.default.green("~")} Updated ${chalk_1.default.bold("vitest.config.ts")} — added tricklePlugin() for variable tracing`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Step 6: Update package.json scripts
|
|
363
488
|
if (info.hasPackageJson) {
|
|
364
489
|
const { scriptsAdded } = updatePackageJson(dir, info);
|
|
365
490
|
if (scriptsAdded.length > 0) {
|
|
@@ -368,12 +493,12 @@ async function initCommand(opts) {
|
|
|
368
493
|
}
|
|
369
494
|
}
|
|
370
495
|
}
|
|
371
|
-
// Step
|
|
496
|
+
// Step 7: Update .gitignore
|
|
372
497
|
const giUpdated = updateGitignore(dir);
|
|
373
498
|
if (giUpdated) {
|
|
374
499
|
console.log(` ${chalk_1.default.green("~")} Updated ${chalk_1.default.bold(".gitignore")} — added .trickle/`);
|
|
375
500
|
}
|
|
376
|
-
// Step
|
|
501
|
+
// Step 8: Print next steps
|
|
377
502
|
console.log("");
|
|
378
503
|
console.log(chalk_1.default.bold(" Next steps:"));
|
|
379
504
|
console.log("");
|
|
@@ -389,6 +514,7 @@ async function initCommand(opts) {
|
|
|
389
514
|
console.log("");
|
|
390
515
|
console.log(chalk_1.default.gray(" Other commands:"));
|
|
391
516
|
console.log(chalk_1.default.gray(" trickle functions — list observed functions"));
|
|
517
|
+
console.log(chalk_1.default.gray(" trickle vars — list captured variable types + values"));
|
|
392
518
|
console.log(chalk_1.default.gray(" trickle types <name> — see types + sample data"));
|
|
393
519
|
console.log(chalk_1.default.gray(" trickle annotate src/ — add type annotations to source files"));
|
|
394
520
|
console.log("");
|
package/dist/commands/run.js
CHANGED
|
@@ -150,10 +150,15 @@ function autoDetectCommand(input) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
function findTsRunner() {
|
|
153
|
+
const { execSync } = require("child_process");
|
|
154
|
+
// Add node_modules/.bin to PATH so local binaries are found
|
|
155
|
+
const binPath = path.join(process.cwd(), "node_modules", ".bin");
|
|
156
|
+
const currentPath = process.env.PATH || "";
|
|
157
|
+
const augmentedPath = currentPath.includes(binPath) ? currentPath : `${binPath}${path.delimiter}${currentPath}`;
|
|
158
|
+
const execOpts = { stdio: "ignore", env: { ...process.env, PATH: augmentedPath } };
|
|
153
159
|
// Check for tsx (fastest, most compatible)
|
|
154
160
|
try {
|
|
155
|
-
|
|
156
|
-
execSync("tsx --version", { stdio: "ignore" });
|
|
161
|
+
execSync("tsx --version", execOpts);
|
|
157
162
|
return "tsx";
|
|
158
163
|
}
|
|
159
164
|
catch {
|
|
@@ -161,8 +166,7 @@ function findTsRunner() {
|
|
|
161
166
|
}
|
|
162
167
|
// Check for ts-node
|
|
163
168
|
try {
|
|
164
|
-
|
|
165
|
-
execSync("ts-node --version", { stdio: "ignore" });
|
|
169
|
+
execSync("ts-node --version", execOpts);
|
|
166
170
|
return "ts-node";
|
|
167
171
|
}
|
|
168
172
|
catch {
|
|
@@ -170,8 +174,7 @@ function findTsRunner() {
|
|
|
170
174
|
}
|
|
171
175
|
// Check for bun (supports TS natively)
|
|
172
176
|
try {
|
|
173
|
-
|
|
174
|
-
execSync("bun --version", { stdio: "ignore" });
|
|
177
|
+
execSync("bun --version", execOpts);
|
|
175
178
|
return "bun";
|
|
176
179
|
}
|
|
177
180
|
catch {
|
|
@@ -388,6 +391,17 @@ async function executeSingleRun(instrumentedCommand, env, opts, singleFile, loca
|
|
|
388
391
|
console.log(chalk_1.default.green(` Types written to ${chalk_1.default.bold(relPath)}`));
|
|
389
392
|
}
|
|
390
393
|
}
|
|
394
|
+
// Show variable/tensor summary if variables.jsonl exists
|
|
395
|
+
const varsJsonlPath = path.join(localDir, "variables.jsonl");
|
|
396
|
+
if (fs.existsSync(varsJsonlPath)) {
|
|
397
|
+
try {
|
|
398
|
+
const { showVarsSummary } = await Promise.resolve().then(() => __importStar(require("./vars")));
|
|
399
|
+
showVarsSummary(varsJsonlPath);
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// vars module not available, skip
|
|
403
|
+
}
|
|
404
|
+
}
|
|
391
405
|
console.log(chalk_1.default.gray(" " + "─".repeat(50)));
|
|
392
406
|
console.log("");
|
|
393
407
|
return exitCode;
|
|
@@ -471,14 +485,19 @@ function startLiveBackendTypes(sourceFile) {
|
|
|
471
485
|
const baseName = path.basename(sourceFile, ext);
|
|
472
486
|
const sidecarName = isPython ? `${baseName}.pyi` : `${baseName}.d.ts`;
|
|
473
487
|
const sidecarPath = path.join(dir, sidecarName);
|
|
488
|
+
// Also check .trickle/types/ where auto-codegen now writes
|
|
489
|
+
const trickleDir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
490
|
+
const trickleTypesPath = path.join(trickleDir, 'types', `${baseName}.d.ts`);
|
|
474
491
|
const poll = async () => {
|
|
475
492
|
if (stopped)
|
|
476
493
|
return;
|
|
477
494
|
try {
|
|
478
495
|
const { stubsCommand } = await Promise.resolve().then(() => __importStar(require("./stubs")));
|
|
479
496
|
await stubsCommand(dir, { silent: true });
|
|
480
|
-
|
|
481
|
-
|
|
497
|
+
// Check both old sidecar path and new .trickle/types/ path
|
|
498
|
+
const effectivePath = fs.existsSync(trickleTypesPath) ? trickleTypesPath : sidecarPath;
|
|
499
|
+
if (fs.existsSync(effectivePath)) {
|
|
500
|
+
const content = fs.readFileSync(effectivePath, "utf-8");
|
|
482
501
|
const funcCount = (content.match(/export declare function/g) || []).length;
|
|
483
502
|
if (funcCount > lastFunctionCount) {
|
|
484
503
|
const newCount = funcCount - lastFunctionCount;
|
|
@@ -552,11 +571,15 @@ async function autoGenerateSidecar(filePath) {
|
|
|
552
571
|
// Use the stubs command to generate stubs for the file's directory
|
|
553
572
|
const { stubsCommand } = await Promise.resolve().then(() => __importStar(require("./stubs")));
|
|
554
573
|
await stubsCommand(dir, { silent: true });
|
|
555
|
-
// Check if
|
|
556
|
-
|
|
557
|
-
|
|
574
|
+
// Check if types were generated (either sidecar or .trickle/types/)
|
|
575
|
+
const tDir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
576
|
+
const tTypesPath = path.join(tDir, 'types', `${baseName}.d.ts`);
|
|
577
|
+
const effectiveSidecar = fs.existsSync(tTypesPath) ? tTypesPath : sidecarPath;
|
|
578
|
+
const displayName = fs.existsSync(tTypesPath) ? `${baseName}.d.ts` : sidecarName;
|
|
579
|
+
if (fs.existsSync(effectiveSidecar)) {
|
|
580
|
+
const stats = fs.statSync(effectiveSidecar);
|
|
558
581
|
if (stats.size > 0) {
|
|
559
|
-
console.log(chalk_1.default.green(`\n Types written to ${chalk_1.default.bold(
|
|
582
|
+
console.log(chalk_1.default.green(`\n Types written to ${chalk_1.default.bold(displayName)}`));
|
|
560
583
|
}
|
|
561
584
|
}
|
|
562
585
|
}
|
|
@@ -828,7 +851,13 @@ function injectObservation(command, backendUrl, opts) {
|
|
|
828
851
|
const runner = nodeMatch[1];
|
|
829
852
|
const useEsm = isEsmFile(command) && observeEsmPath;
|
|
830
853
|
if (useEsm) {
|
|
831
|
-
|
|
854
|
+
// Use both ESM hooks (for exported functions) and CJS hook (for Express auto-detection)
|
|
855
|
+
const modified = command.replace(new RegExp(`^${runner}\\s`), `${runner} -r ${observePath} --import ${observeEsmPath} `);
|
|
856
|
+
return { instrumentedCommand: modified, env };
|
|
857
|
+
}
|
|
858
|
+
else if (runner === "tsx") {
|
|
859
|
+
// tsx always uses ESM internally — inject both CJS and ESM hooks
|
|
860
|
+
const modified = command.replace(new RegExp(`^${runner}\\s`), `${runner} -r ${observePath} --import ${observeEsmPath} `);
|
|
832
861
|
return { instrumentedCommand: modified, env };
|
|
833
862
|
}
|
|
834
863
|
else {
|
|
@@ -907,10 +936,14 @@ function resolveObserveEsmPath() {
|
|
|
907
936
|
}
|
|
908
937
|
function runProcess(command, env) {
|
|
909
938
|
return new Promise((resolve) => {
|
|
939
|
+
// Add node_modules/.bin to PATH so local binaries (tsx, ts-node, etc.) are found
|
|
940
|
+
const binPath = path.join(process.cwd(), "node_modules", ".bin");
|
|
941
|
+
const currentPath = process.env.PATH || "";
|
|
942
|
+
const augmentedPath = currentPath.includes(binPath) ? currentPath : `${binPath}${path.delimiter}${currentPath}`;
|
|
910
943
|
const proc = (0, child_process_1.spawn)(command, [], {
|
|
911
944
|
stdio: "inherit",
|
|
912
945
|
shell: true,
|
|
913
|
-
env: { ...process.env, ...env },
|
|
946
|
+
env: { ...process.env, ...env, PATH: augmentedPath },
|
|
914
947
|
});
|
|
915
948
|
proc.on("error", (err) => {
|
|
916
949
|
console.error(chalk_1.default.red(`\n Failed to start: ${err.message}\n`));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface VarsOptions {
|
|
2
|
+
file?: string;
|
|
3
|
+
module?: string;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
tensors?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function varsCommand(opts: VarsOptions): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Show a brief post-run summary of traced variables (especially tensors).
|
|
10
|
+
* Called by `trickle run` after the user's command finishes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function showVarsSummary(varsFile: string): void;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.varsCommand = varsCommand;
|
|
40
|
+
exports.showVarsSummary = showVarsSummary;
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
function renderType(node, depth = 0) {
|
|
46
|
+
if (depth > 3)
|
|
47
|
+
return "...";
|
|
48
|
+
switch (node.kind) {
|
|
49
|
+
case "primitive":
|
|
50
|
+
return node.name || "unknown";
|
|
51
|
+
case "array":
|
|
52
|
+
return `${renderType(node.element, depth + 1)}[]`;
|
|
53
|
+
case "object": {
|
|
54
|
+
const props = node.properties || {};
|
|
55
|
+
const keys = Object.keys(props);
|
|
56
|
+
if (keys.length === 0)
|
|
57
|
+
return node.class_name || "{}";
|
|
58
|
+
// Special types
|
|
59
|
+
if (keys.length === 1 && keys[0].startsWith("__")) {
|
|
60
|
+
if (keys[0] === "__date")
|
|
61
|
+
return "Date";
|
|
62
|
+
if (keys[0] === "__regexp")
|
|
63
|
+
return "RegExp";
|
|
64
|
+
if (keys[0] === "__error")
|
|
65
|
+
return "Error";
|
|
66
|
+
if (keys[0] === "__buffer")
|
|
67
|
+
return "Buffer";
|
|
68
|
+
}
|
|
69
|
+
// Tensor / ndarray — show as "Tensor[1, 16, 32] float32"
|
|
70
|
+
if (node.class_name === "Tensor" || node.class_name === "ndarray") {
|
|
71
|
+
return renderTensorType(node.class_name, props);
|
|
72
|
+
}
|
|
73
|
+
// Named class — show as "ClassName(field=type, ...)"
|
|
74
|
+
if (node.class_name) {
|
|
75
|
+
if (keys.length > 4) {
|
|
76
|
+
const shown = keys.slice(0, 3).map((k) => `${k}=${renderType(props[k], depth + 1)}`);
|
|
77
|
+
return `${node.class_name}(${shown.join(", ")}, ...)`;
|
|
78
|
+
}
|
|
79
|
+
const entries = keys.map((k) => `${k}=${renderType(props[k], depth + 1)}`);
|
|
80
|
+
return `${node.class_name}(${entries.join(", ")})`;
|
|
81
|
+
}
|
|
82
|
+
if (keys.length > 4) {
|
|
83
|
+
const shown = keys.slice(0, 3).map((k) => `${k}: ${renderType(props[k], depth + 1)}`);
|
|
84
|
+
return `{ ${shown.join(", ")}, ... }`;
|
|
85
|
+
}
|
|
86
|
+
const entries = keys.map((k) => `${k}: ${renderType(props[k], depth + 1)}`);
|
|
87
|
+
return `{ ${entries.join(", ")} }`;
|
|
88
|
+
}
|
|
89
|
+
case "union":
|
|
90
|
+
return (node.members || []).map((m) => renderType(m, depth + 1)).join(" | ");
|
|
91
|
+
case "tuple":
|
|
92
|
+
return `[${(node.elements || []).map((e) => renderType(e, depth + 1)).join(", ")}]`;
|
|
93
|
+
case "promise":
|
|
94
|
+
return `Promise<${renderType(node.resolved, depth + 1)}>`;
|
|
95
|
+
case "function":
|
|
96
|
+
if (node.name && node.name !== "anonymous")
|
|
97
|
+
return `${node.name}(...)`;
|
|
98
|
+
return "Function";
|
|
99
|
+
case "map":
|
|
100
|
+
return `Map<${renderType(node.key, depth + 1)}, ${renderType(node.value, depth + 1)}>`;
|
|
101
|
+
case "set":
|
|
102
|
+
return `Set<${renderType(node.element, depth + 1)}>`;
|
|
103
|
+
default:
|
|
104
|
+
return "unknown";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Format a tensor type as "Tensor[1, 16, 32] float32 @cuda:0" */
|
|
108
|
+
function renderTensorType(className, properties) {
|
|
109
|
+
const parts = [className];
|
|
110
|
+
const shapeProp = properties["shape"];
|
|
111
|
+
if (shapeProp?.kind === "primitive" && shapeProp.name) {
|
|
112
|
+
parts[0] = `${className}${shapeProp.name}`;
|
|
113
|
+
}
|
|
114
|
+
const dtypeProp = properties["dtype"];
|
|
115
|
+
if (dtypeProp?.kind === "primitive" && dtypeProp.name) {
|
|
116
|
+
let dtype = dtypeProp.name;
|
|
117
|
+
dtype = dtype.replace("torch.", "").replace("numpy.", "");
|
|
118
|
+
parts.push(dtype);
|
|
119
|
+
}
|
|
120
|
+
const deviceProp = properties["device"];
|
|
121
|
+
if (deviceProp?.kind === "primitive" && deviceProp.name && deviceProp.name !== "cpu") {
|
|
122
|
+
parts.push(`@${deviceProp.name}`);
|
|
123
|
+
}
|
|
124
|
+
return parts.join(" ");
|
|
125
|
+
}
|
|
126
|
+
/** Check if a TypeNode represents a tensor type */
|
|
127
|
+
function isTensorType(node) {
|
|
128
|
+
return node.kind === "object" && (node.class_name === "Tensor" || node.class_name === "ndarray");
|
|
129
|
+
}
|
|
130
|
+
function renderSample(sample) {
|
|
131
|
+
if (sample === null)
|
|
132
|
+
return "null";
|
|
133
|
+
if (sample === undefined)
|
|
134
|
+
return "undefined";
|
|
135
|
+
const str = JSON.stringify(sample);
|
|
136
|
+
if (str.length > 60)
|
|
137
|
+
return str.substring(0, 57) + "...";
|
|
138
|
+
return str;
|
|
139
|
+
}
|
|
140
|
+
async function varsCommand(opts) {
|
|
141
|
+
const trickleDir = path.join(process.cwd(), ".trickle");
|
|
142
|
+
const varsFile = path.join(trickleDir, "variables.jsonl");
|
|
143
|
+
if (!fs.existsSync(varsFile)) {
|
|
144
|
+
console.log(chalk_1.default.yellow("\n No variable observations found."));
|
|
145
|
+
console.log(chalk_1.default.gray(" Run your code with: trickle run <command>\n"));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const content = fs.readFileSync(varsFile, "utf-8");
|
|
149
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
150
|
+
const observations = [];
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
try {
|
|
153
|
+
const obs = JSON.parse(line);
|
|
154
|
+
if (obs.kind === "variable")
|
|
155
|
+
observations.push(obs);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// skip malformed lines
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (observations.length === 0) {
|
|
162
|
+
console.log(chalk_1.default.yellow("\n No variable observations found.\n"));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Filter
|
|
166
|
+
let filtered = observations;
|
|
167
|
+
if (opts.file) {
|
|
168
|
+
const fileFilter = opts.file;
|
|
169
|
+
filtered = filtered.filter((o) => o.file.includes(fileFilter) || o.module.includes(fileFilter));
|
|
170
|
+
}
|
|
171
|
+
if (opts.module) {
|
|
172
|
+
filtered = filtered.filter((o) => o.module === opts.module);
|
|
173
|
+
}
|
|
174
|
+
if (opts.tensors) {
|
|
175
|
+
filtered = filtered.filter((o) => isTensorType(o.type));
|
|
176
|
+
}
|
|
177
|
+
if (filtered.length === 0) {
|
|
178
|
+
console.log(chalk_1.default.yellow("\n No matching variables found.\n"));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (opts.json) {
|
|
182
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Group by file
|
|
186
|
+
const byFile = new Map();
|
|
187
|
+
for (const obs of filtered) {
|
|
188
|
+
const key = obs.file;
|
|
189
|
+
if (!byFile.has(key))
|
|
190
|
+
byFile.set(key, []);
|
|
191
|
+
byFile.get(key).push(obs);
|
|
192
|
+
}
|
|
193
|
+
// Sort each file's vars by line number
|
|
194
|
+
for (const [, vars] of byFile) {
|
|
195
|
+
vars.sort((a, b) => a.line - b.line);
|
|
196
|
+
}
|
|
197
|
+
console.log("");
|
|
198
|
+
for (const [file, vars] of byFile) {
|
|
199
|
+
// Show relative path
|
|
200
|
+
const relPath = path.relative(process.cwd(), file);
|
|
201
|
+
console.log(chalk_1.default.cyan.bold(` ${relPath}`));
|
|
202
|
+
console.log("");
|
|
203
|
+
const table = new cli_table3_1.default({
|
|
204
|
+
head: [
|
|
205
|
+
chalk_1.default.gray("Line"),
|
|
206
|
+
chalk_1.default.gray("Variable"),
|
|
207
|
+
chalk_1.default.gray("Type"),
|
|
208
|
+
chalk_1.default.gray("Sample Value"),
|
|
209
|
+
],
|
|
210
|
+
style: { head: [], border: ["gray"] },
|
|
211
|
+
colWidths: [8, 20, 35, 40],
|
|
212
|
+
wordWrap: true,
|
|
213
|
+
chars: {
|
|
214
|
+
top: "─",
|
|
215
|
+
"top-mid": "┬",
|
|
216
|
+
"top-left": "┌",
|
|
217
|
+
"top-right": "┐",
|
|
218
|
+
bottom: "─",
|
|
219
|
+
"bottom-mid": "┴",
|
|
220
|
+
"bottom-left": "└",
|
|
221
|
+
"bottom-right": "┘",
|
|
222
|
+
left: "│",
|
|
223
|
+
"left-mid": "├",
|
|
224
|
+
mid: "─",
|
|
225
|
+
"mid-mid": "┼",
|
|
226
|
+
right: "│",
|
|
227
|
+
"right-mid": "┤",
|
|
228
|
+
middle: "│",
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
for (const v of vars) {
|
|
232
|
+
table.push([
|
|
233
|
+
chalk_1.default.gray(String(v.line)),
|
|
234
|
+
chalk_1.default.white.bold(v.varName),
|
|
235
|
+
chalk_1.default.green(renderType(v.type)),
|
|
236
|
+
chalk_1.default.gray(renderSample(v.sample)),
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
console.log(table.toString());
|
|
240
|
+
console.log("");
|
|
241
|
+
}
|
|
242
|
+
// Summary
|
|
243
|
+
const totalVars = filtered.length;
|
|
244
|
+
const totalFiles = byFile.size;
|
|
245
|
+
const tensorCount = filtered.filter((o) => isTensorType(o.type)).length;
|
|
246
|
+
const summaryParts = [`${totalVars} variable(s) across ${totalFiles} file(s)`];
|
|
247
|
+
if (tensorCount > 0) {
|
|
248
|
+
summaryParts.push(`${tensorCount} tensor(s)`);
|
|
249
|
+
}
|
|
250
|
+
console.log(chalk_1.default.gray(` ${summaryParts.join(", ")}\n`));
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Show a brief post-run summary of traced variables (especially tensors).
|
|
254
|
+
* Called by `trickle run` after the user's command finishes.
|
|
255
|
+
*/
|
|
256
|
+
function showVarsSummary(varsFile) {
|
|
257
|
+
if (!fs.existsSync(varsFile))
|
|
258
|
+
return;
|
|
259
|
+
const content = fs.readFileSync(varsFile, "utf-8");
|
|
260
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
261
|
+
const observations = [];
|
|
262
|
+
for (const line of lines) {
|
|
263
|
+
try {
|
|
264
|
+
const obs = JSON.parse(line);
|
|
265
|
+
if (obs.kind === "variable")
|
|
266
|
+
observations.push(obs);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// skip
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (observations.length === 0)
|
|
273
|
+
return;
|
|
274
|
+
const tensorObs = observations.filter((o) => isTensorType(o.type));
|
|
275
|
+
const byFile = new Map();
|
|
276
|
+
for (const obs of tensorObs) {
|
|
277
|
+
if (!byFile.has(obs.file))
|
|
278
|
+
byFile.set(obs.file, []);
|
|
279
|
+
byFile.get(obs.file).push(obs);
|
|
280
|
+
}
|
|
281
|
+
// Sort by line within each file
|
|
282
|
+
for (const [, vars] of byFile) {
|
|
283
|
+
vars.sort((a, b) => a.line - b.line);
|
|
284
|
+
}
|
|
285
|
+
console.log(` Variables traced: ${chalk_1.default.bold(String(observations.length))}`);
|
|
286
|
+
if (tensorObs.length > 0) {
|
|
287
|
+
console.log(` Tensor variables: ${chalk_1.default.bold(String(tensorObs.length))}`);
|
|
288
|
+
console.log("");
|
|
289
|
+
// Show up to 15 most interesting tensor variables
|
|
290
|
+
const shown = [];
|
|
291
|
+
for (const [, vars] of byFile) {
|
|
292
|
+
shown.push(...vars);
|
|
293
|
+
}
|
|
294
|
+
const toShow = shown.slice(0, 15);
|
|
295
|
+
for (const obs of toShow) {
|
|
296
|
+
const relPath = path.relative(process.cwd(), obs.file);
|
|
297
|
+
const typeStr = renderType(obs.type);
|
|
298
|
+
console.log(` ${chalk_1.default.gray(`${relPath}:${obs.line}`)} ${chalk_1.default.white.bold(obs.varName)} ${chalk_1.default.green(typeStr)}`);
|
|
299
|
+
}
|
|
300
|
+
if (shown.length > 15) {
|
|
301
|
+
console.log(chalk_1.default.gray(` ... and ${shown.length - 15} more`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
console.log(chalk_1.default.gray(` Run ${chalk_1.default.white("trickle vars")} for full details`));
|
|
305
|
+
}
|