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.
@@ -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?|python3?\.\d+|vitest|jest|mocha|npx|bunx|pytest|uvicorn|gunicorn|flask|django-admin)\b/.test(input)) {
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?|python3?\.\d+)\s/);
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.223",
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",
@@ -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?|python3?\.\d+|vitest|jest|mocha|npx|bunx|pytest|uvicorn|gunicorn|flask|django-admin)\b/.test(input)) {
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?|python3?\.\d+)\s/);
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");