trickle-cli 0.1.223 → 0.1.225
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/run.js +37 -2
- package/package.json +1 -1
- package/src/commands/run.ts +47 -3
package/dist/commands/run.js
CHANGED
|
@@ -161,7 +161,7 @@ function detectSingleFile(command) {
|
|
|
161
161
|
// ── Auto-detect runtime from file extension ──
|
|
162
162
|
function autoDetectCommand(input) {
|
|
163
163
|
// If it already starts with a known runtime, return as-is
|
|
164
|
-
if (/^(node|ts-node|tsx|nodemon|bun|deno|python3?|
|
|
164
|
+
if (/^((?:[\w./~-]+\/)?(node|ts-node|tsx|nodemon|bun|deno|python3?(?:\.\d+)?|vitest|jest|mocha|npx|bunx|pytest|uvicorn|gunicorn|flask|django-admin))\b/.test(input)) {
|
|
165
165
|
return input;
|
|
166
166
|
}
|
|
167
167
|
// Check if the first token is a file path
|
|
@@ -1208,7 +1208,7 @@ function injectObservation(command, backendUrl, opts) {
|
|
|
1208
1208
|
}
|
|
1209
1209
|
return { instrumentedCommand: command, env };
|
|
1210
1210
|
}
|
|
1211
|
-
const pyMatch = command.match(/^(python3
|
|
1211
|
+
const pyMatch = command.match(/^((?:[\w./~-]+\/)?python3?(?:\.\d+)?)\s/);
|
|
1212
1212
|
if (pyMatch) {
|
|
1213
1213
|
const python = pyMatch[1];
|
|
1214
1214
|
const rest = command.slice(pyMatch[0].length);
|
|
@@ -1219,6 +1219,9 @@ function injectObservation(command, backendUrl, opts) {
|
|
|
1219
1219
|
// Auto-enable terminal type summary when running via trickle run
|
|
1220
1220
|
if (!process.env.TRICKLE_SUMMARY)
|
|
1221
1221
|
env.TRICKLE_SUMMARY = "1";
|
|
1222
|
+
// Ensure trickle is importable even if the target Python is a venv without it.
|
|
1223
|
+
// Find trickle's install location from any available system Python and inject via PYTHONPATH.
|
|
1224
|
+
ensureTricklePythonPath(python, env);
|
|
1222
1225
|
return {
|
|
1223
1226
|
instrumentedCommand: `${python} -c "from trickle.observe_runner import main; main()" ${rest}`,
|
|
1224
1227
|
env,
|
|
@@ -1229,6 +1232,7 @@ function injectObservation(command, backendUrl, opts) {
|
|
|
1229
1232
|
env.TRICKLE_OBSERVE_INCLUDE = opts.include;
|
|
1230
1233
|
if (opts.exclude)
|
|
1231
1234
|
env.TRICKLE_OBSERVE_EXCLUDE = opts.exclude;
|
|
1235
|
+
ensureTricklePythonPath("python", env);
|
|
1232
1236
|
return {
|
|
1233
1237
|
instrumentedCommand: `python -c "from trickle.observe_runner import main; main()" -m ${command}`,
|
|
1234
1238
|
env,
|
|
@@ -1239,6 +1243,37 @@ function injectObservation(command, backendUrl, opts) {
|
|
|
1239
1243
|
env.NODE_OPTIONS = `${existing} -r ${observePath}`.trim();
|
|
1240
1244
|
return { instrumentedCommand: command, env };
|
|
1241
1245
|
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Ensure the target Python can import trickle by adding the trickle package's
|
|
1248
|
+
* site-packages to PYTHONPATH. This handles the case where the user runs a venv
|
|
1249
|
+
* Python that doesn't have trickle-observe installed — we find it from the system
|
|
1250
|
+
* Python (or any Python that has it) and inject the path.
|
|
1251
|
+
*/
|
|
1252
|
+
function ensureTricklePythonPath(targetPython, env) {
|
|
1253
|
+
// First check if the target Python already has trickle
|
|
1254
|
+
try {
|
|
1255
|
+
(0, child_process_1.execSync)(`${targetPython} -c "import trickle" 2>/dev/null`, { stdio: "ignore", timeout: 5000 });
|
|
1256
|
+
return; // trickle is already importable
|
|
1257
|
+
}
|
|
1258
|
+
catch {
|
|
1259
|
+
// Not available in target Python — find it elsewhere
|
|
1260
|
+
}
|
|
1261
|
+
// Try common Python commands to find where trickle is installed
|
|
1262
|
+
const candidates = ["python3", "python", "python3.11", "python3.12", "python3.13", "python3.10"];
|
|
1263
|
+
for (const py of candidates) {
|
|
1264
|
+
try {
|
|
1265
|
+
const sitePath = (0, child_process_1.execSync)(`${py} -c "import trickle, os; print(os.path.dirname(os.path.dirname(trickle.__file__)))"`, { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1266
|
+
if (sitePath) {
|
|
1267
|
+
const existing = env.PYTHONPATH || process.env.PYTHONPATH || "";
|
|
1268
|
+
env.PYTHONPATH = existing ? `${sitePath}:${existing}` : sitePath;
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
catch {
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1242
1277
|
function resolveObservePath() {
|
|
1243
1278
|
try {
|
|
1244
1279
|
return require.resolve("trickle-observe/observe");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trickle-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.225",
|
|
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/run.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import * as fs from "fs";
|
|
3
|
-
import { spawn, ChildProcess } from "child_process";
|
|
3
|
+
import { spawn, execSync, ChildProcess } from "child_process";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { getBackendUrl } from "../config";
|
|
6
6
|
import {
|
|
@@ -153,7 +153,7 @@ function detectSingleFile(command: string): string | null {
|
|
|
153
153
|
|
|
154
154
|
function autoDetectCommand(input: string): string {
|
|
155
155
|
// If it already starts with a known runtime, return as-is
|
|
156
|
-
if (/^(node|ts-node|tsx|nodemon|bun|deno|python3?|
|
|
156
|
+
if (/^((?:[\w./~-]+\/)?(node|ts-node|tsx|nodemon|bun|deno|python3?(?:\.\d+)?|vitest|jest|mocha|npx|bunx|pytest|uvicorn|gunicorn|flask|django-admin))\b/.test(input)) {
|
|
157
157
|
return input;
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -1328,7 +1328,7 @@ function injectObservation(
|
|
|
1328
1328
|
return { instrumentedCommand: command, env };
|
|
1329
1329
|
}
|
|
1330
1330
|
|
|
1331
|
-
const pyMatch = command.match(/^(python3
|
|
1331
|
+
const pyMatch = command.match(/^((?:[\w./~-]+\/)?python3?(?:\.\d+)?)\s/);
|
|
1332
1332
|
if (pyMatch) {
|
|
1333
1333
|
const python = pyMatch[1];
|
|
1334
1334
|
const rest = command.slice(pyMatch[0].length);
|
|
@@ -1336,6 +1336,9 @@ function injectObservation(
|
|
|
1336
1336
|
if (opts.exclude) env.TRICKLE_OBSERVE_EXCLUDE = opts.exclude;
|
|
1337
1337
|
// Auto-enable terminal type summary when running via trickle run
|
|
1338
1338
|
if (!process.env.TRICKLE_SUMMARY) env.TRICKLE_SUMMARY = "1";
|
|
1339
|
+
// Ensure trickle is importable even if the target Python is a venv without it.
|
|
1340
|
+
// Find trickle's install location from any available system Python and inject via PYTHONPATH.
|
|
1341
|
+
ensureTricklePythonPath(python, env);
|
|
1339
1342
|
return {
|
|
1340
1343
|
instrumentedCommand: `${python} -c "from trickle.observe_runner import main; main()" ${rest}`,
|
|
1341
1344
|
env,
|
|
@@ -1345,6 +1348,7 @@ function injectObservation(
|
|
|
1345
1348
|
if (/^(pytest|uvicorn|gunicorn|flask|django-admin)\b/.test(command)) {
|
|
1346
1349
|
if (opts.include) env.TRICKLE_OBSERVE_INCLUDE = opts.include;
|
|
1347
1350
|
if (opts.exclude) env.TRICKLE_OBSERVE_EXCLUDE = opts.exclude;
|
|
1351
|
+
ensureTricklePythonPath("python", env);
|
|
1348
1352
|
return {
|
|
1349
1353
|
instrumentedCommand: `python -c "from trickle.observe_runner import main; main()" -m ${command}`,
|
|
1350
1354
|
env,
|
|
@@ -1361,6 +1365,46 @@ function injectObservation(
|
|
|
1361
1365
|
return { instrumentedCommand: command, env };
|
|
1362
1366
|
}
|
|
1363
1367
|
|
|
1368
|
+
/**
|
|
1369
|
+
* Ensure the target Python can import trickle by adding the trickle package's
|
|
1370
|
+
* site-packages to PYTHONPATH. This handles the case where the user runs a venv
|
|
1371
|
+
* Python that doesn't have trickle-observe installed — we find it from the system
|
|
1372
|
+
* Python (or any Python that has it) and inject the path.
|
|
1373
|
+
*/
|
|
1374
|
+
function ensureTricklePythonPath(
|
|
1375
|
+
targetPython: string,
|
|
1376
|
+
env: Record<string, string>,
|
|
1377
|
+
): void {
|
|
1378
|
+
// First check if the target Python already has trickle
|
|
1379
|
+
try {
|
|
1380
|
+
execSync(
|
|
1381
|
+
`${targetPython} -c "import trickle" 2>/dev/null`,
|
|
1382
|
+
{ stdio: "ignore", timeout: 5000 },
|
|
1383
|
+
);
|
|
1384
|
+
return; // trickle is already importable
|
|
1385
|
+
} catch {
|
|
1386
|
+
// Not available in target Python — find it elsewhere
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// Try common Python commands to find where trickle is installed
|
|
1390
|
+
const candidates = ["python3", "python", "python3.11", "python3.12", "python3.13", "python3.10"];
|
|
1391
|
+
for (const py of candidates) {
|
|
1392
|
+
try {
|
|
1393
|
+
const sitePath = execSync(
|
|
1394
|
+
`${py} -c "import trickle, os; print(os.path.dirname(os.path.dirname(trickle.__file__)))"`,
|
|
1395
|
+
{ encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] },
|
|
1396
|
+
).trim();
|
|
1397
|
+
if (sitePath) {
|
|
1398
|
+
const existing = env.PYTHONPATH || process.env.PYTHONPATH || "";
|
|
1399
|
+
env.PYTHONPATH = existing ? `${sitePath}:${existing}` : sitePath;
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
} catch {
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1364
1408
|
function resolveObservePath(): string {
|
|
1365
1409
|
try {
|
|
1366
1410
|
return require.resolve("trickle-observe/observe");
|