trickle-cli 0.1.224 → 0.1.226

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.
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.runCommand = runCommand;
40
40
  const path = __importStar(require("path"));
41
41
  const fs = __importStar(require("fs"));
42
+ const os = __importStar(require("os"));
42
43
  const child_process_1 = require("child_process");
43
44
  const chalk_1 = __importDefault(require("chalk"));
44
45
  const config_1 = require("../config");
@@ -1219,6 +1220,9 @@ function injectObservation(command, backendUrl, opts) {
1219
1220
  // Auto-enable terminal type summary when running via trickle run
1220
1221
  if (!process.env.TRICKLE_SUMMARY)
1221
1222
  env.TRICKLE_SUMMARY = "1";
1223
+ // Ensure trickle is importable even if the target Python is a venv without it.
1224
+ // Find trickle's install location from any available system Python and inject via PYTHONPATH.
1225
+ ensureTricklePythonPath(python, env);
1222
1226
  return {
1223
1227
  instrumentedCommand: `${python} -c "from trickle.observe_runner import main; main()" ${rest}`,
1224
1228
  env,
@@ -1229,6 +1233,7 @@ function injectObservation(command, backendUrl, opts) {
1229
1233
  env.TRICKLE_OBSERVE_INCLUDE = opts.include;
1230
1234
  if (opts.exclude)
1231
1235
  env.TRICKLE_OBSERVE_EXCLUDE = opts.exclude;
1236
+ ensureTricklePythonPath("python", env);
1232
1237
  return {
1233
1238
  instrumentedCommand: `python -c "from trickle.observe_runner import main; main()" -m ${command}`,
1234
1239
  env,
@@ -1239,6 +1244,43 @@ function injectObservation(command, backendUrl, opts) {
1239
1244
  env.NODE_OPTIONS = `${existing} -r ${observePath}`.trim();
1240
1245
  return { instrumentedCommand: command, env };
1241
1246
  }
1247
+ /**
1248
+ * Ensure the target Python can import trickle by creating an isolated temp
1249
+ * directory with a symlink to just the trickle package and adding it to PYTHONPATH.
1250
+ * This avoids polluting the target Python with the entire system site-packages,
1251
+ * which would cause binary incompatibilities across Python versions.
1252
+ */
1253
+ function ensureTricklePythonPath(targetPython, env) {
1254
+ // First check if the target Python already has trickle
1255
+ try {
1256
+ (0, child_process_1.execSync)(`${targetPython} -c "import trickle" 2>/dev/null`, { stdio: "ignore", timeout: 5000 });
1257
+ return; // trickle is already importable
1258
+ }
1259
+ catch {
1260
+ // Not available in target Python — find it elsewhere
1261
+ }
1262
+ // Try common Python commands to find where trickle is installed
1263
+ const candidates = ["python3", "python", "python3.11", "python3.12", "python3.13", "python3.10"];
1264
+ for (const py of candidates) {
1265
+ try {
1266
+ // Get the trickle package directory (e.g. /opt/anaconda3/.../site-packages/trickle)
1267
+ const trickleDir = (0, child_process_1.execSync)(`${py} -c "import trickle, os; print(os.path.dirname(trickle.__file__))"`, { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }).trim();
1268
+ if (trickleDir && fs.existsSync(trickleDir)) {
1269
+ // Create a temp directory with just a symlink to the trickle package.
1270
+ // This ensures only trickle (pure Python) is visible, not incompatible
1271
+ // binary packages from a different Python version's site-packages.
1272
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "trickle-pypath-"));
1273
+ fs.symlinkSync(trickleDir, path.join(tmpDir, "trickle"));
1274
+ const existing = env.PYTHONPATH || process.env.PYTHONPATH || "";
1275
+ env.PYTHONPATH = existing ? `${tmpDir}:${existing}` : tmpDir;
1276
+ return;
1277
+ }
1278
+ }
1279
+ catch {
1280
+ continue;
1281
+ }
1282
+ }
1283
+ }
1242
1284
  function resolveObservePath() {
1243
1285
  try {
1244
1286
  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.224",
3
+ "version": "0.1.226",
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",
@@ -1,6 +1,7 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
- import { spawn, ChildProcess } from "child_process";
3
+ import * as os from "os";
4
+ import { spawn, execSync, ChildProcess } from "child_process";
4
5
  import chalk from "chalk";
5
6
  import { getBackendUrl } from "../config";
6
7
  import {
@@ -1336,6 +1337,9 @@ function injectObservation(
1336
1337
  if (opts.exclude) env.TRICKLE_OBSERVE_EXCLUDE = opts.exclude;
1337
1338
  // Auto-enable terminal type summary when running via trickle run
1338
1339
  if (!process.env.TRICKLE_SUMMARY) env.TRICKLE_SUMMARY = "1";
1340
+ // Ensure trickle is importable even if the target Python is a venv without it.
1341
+ // Find trickle's install location from any available system Python and inject via PYTHONPATH.
1342
+ ensureTricklePythonPath(python, env);
1339
1343
  return {
1340
1344
  instrumentedCommand: `${python} -c "from trickle.observe_runner import main; main()" ${rest}`,
1341
1345
  env,
@@ -1345,6 +1349,7 @@ function injectObservation(
1345
1349
  if (/^(pytest|uvicorn|gunicorn|flask|django-admin)\b/.test(command)) {
1346
1350
  if (opts.include) env.TRICKLE_OBSERVE_INCLUDE = opts.include;
1347
1351
  if (opts.exclude) env.TRICKLE_OBSERVE_EXCLUDE = opts.exclude;
1352
+ ensureTricklePythonPath("python", env);
1348
1353
  return {
1349
1354
  instrumentedCommand: `python -c "from trickle.observe_runner import main; main()" -m ${command}`,
1350
1355
  env,
@@ -1361,6 +1366,52 @@ function injectObservation(
1361
1366
  return { instrumentedCommand: command, env };
1362
1367
  }
1363
1368
 
1369
+ /**
1370
+ * Ensure the target Python can import trickle by creating an isolated temp
1371
+ * directory with a symlink to just the trickle package and adding it to PYTHONPATH.
1372
+ * This avoids polluting the target Python with the entire system site-packages,
1373
+ * which would cause binary incompatibilities across Python versions.
1374
+ */
1375
+ function ensureTricklePythonPath(
1376
+ targetPython: string,
1377
+ env: Record<string, string>,
1378
+ ): void {
1379
+ // First check if the target Python already has trickle
1380
+ try {
1381
+ execSync(
1382
+ `${targetPython} -c "import trickle" 2>/dev/null`,
1383
+ { stdio: "ignore", timeout: 5000 },
1384
+ );
1385
+ return; // trickle is already importable
1386
+ } catch {
1387
+ // Not available in target Python — find it elsewhere
1388
+ }
1389
+
1390
+ // Try common Python commands to find where trickle is installed
1391
+ const candidates = ["python3", "python", "python3.11", "python3.12", "python3.13", "python3.10"];
1392
+ for (const py of candidates) {
1393
+ try {
1394
+ // Get the trickle package directory (e.g. /opt/anaconda3/.../site-packages/trickle)
1395
+ const trickleDir = execSync(
1396
+ `${py} -c "import trickle, os; print(os.path.dirname(trickle.__file__))"`,
1397
+ { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] },
1398
+ ).trim();
1399
+ if (trickleDir && fs.existsSync(trickleDir)) {
1400
+ // Create a temp directory with just a symlink to the trickle package.
1401
+ // This ensures only trickle (pure Python) is visible, not incompatible
1402
+ // binary packages from a different Python version's site-packages.
1403
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "trickle-pypath-"));
1404
+ fs.symlinkSync(trickleDir, path.join(tmpDir, "trickle"));
1405
+ const existing = env.PYTHONPATH || process.env.PYTHONPATH || "";
1406
+ env.PYTHONPATH = existing ? `${tmpDir}:${existing}` : tmpDir;
1407
+ return;
1408
+ }
1409
+ } catch {
1410
+ continue;
1411
+ }
1412
+ }
1413
+ }
1414
+
1364
1415
  function resolveObservePath(): string {
1365
1416
  try {
1366
1417
  return require.resolve("trickle-observe/observe");