lmnr-cli 0.1.5 → 0.1.7

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/index.cjs CHANGED
@@ -28,12 +28,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
 
29
29
  //#endregion
30
30
  let commander = require("commander");
31
+ let fs = require("fs");
31
32
  let path = require("path");
32
33
  path = __toESM(path);
33
34
  let pino = require("pino");
34
35
  pino = __toESM(pino);
35
36
  let pino_pretty = require("pino-pretty");
36
37
  let uuid = require("uuid");
38
+ let csv_parser = require("csv-parser");
39
+ csv_parser = __toESM(csv_parser);
40
+ let export_to_csv = require("export-to-csv");
41
+ let fs_promises = require("fs/promises");
42
+ fs_promises = __toESM(fs_promises);
37
43
  let chokidar = require("chokidar");
38
44
  chokidar = __toESM(chokidar);
39
45
  let http = require("http");
@@ -45,7 +51,7 @@ let readline = require("readline");
45
51
  readline = __toESM(readline);
46
52
 
47
53
  //#region package.json
48
- var version$1 = "0.1.5";
54
+ var version$1 = "0.1.7";
49
55
 
50
56
  //#endregion
51
57
  //#region ../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv/package.json
@@ -113,7 +119,7 @@ var require_package = /* @__PURE__ */ __commonJSMin(((exports, module) => {
113
119
  //#endregion
114
120
  //#region ../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv/lib/main.js
115
121
  var require_main = /* @__PURE__ */ __commonJSMin(((exports, module) => {
116
- const fs = require("fs");
122
+ const fs$1 = require("fs");
117
123
  const path$1 = require("path");
118
124
  const os = require("os");
119
125
  const crypto$1 = require("crypto");
@@ -250,10 +256,10 @@ var require_main = /* @__PURE__ */ __commonJSMin(((exports, module) => {
250
256
  function _vaultPath(options) {
251
257
  let possibleVaultPath = null;
252
258
  if (options && options.path && options.path.length > 0) if (Array.isArray(options.path)) {
253
- for (const filepath of options.path) if (fs.existsSync(filepath)) possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
259
+ for (const filepath of options.path) if (fs$1.existsSync(filepath)) possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
254
260
  } else possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
255
261
  else possibleVaultPath = path$1.resolve(process.cwd(), ".env.vault");
256
- if (fs.existsSync(possibleVaultPath)) return possibleVaultPath;
262
+ if (fs$1.existsSync(possibleVaultPath)) return possibleVaultPath;
257
263
  return null;
258
264
  }
259
265
  function _resolveHome(envPath) {
@@ -287,7 +293,7 @@ var require_main = /* @__PURE__ */ __commonJSMin(((exports, module) => {
287
293
  let lastError;
288
294
  const parsedAll = {};
289
295
  for (const path$2 of optionPaths) try {
290
- const parsed = DotenvModule.parse(fs.readFileSync(path$2, { encoding }));
296
+ const parsed = DotenvModule.parse(fs$1.readFileSync(path$2, { encoding }));
291
297
  DotenvModule.populate(parsedAll, parsed, options);
292
298
  } catch (e) {
293
299
  if (debug) _debug(`Failed to load ${path$2} ${e.message}`);
@@ -392,7 +398,7 @@ var require_main = /* @__PURE__ */ __commonJSMin(((exports, module) => {
392
398
  //#endregion
393
399
  //#region ../client/dist/index.mjs
394
400
  var import_main = require_main();
395
- var version = "0.8.11";
401
+ var version = "0.8.15";
396
402
  function getLangVersion() {
397
403
  if (typeof process !== "undefined" && process.versions && process.versions.node) return `node-${process.versions.node}`;
398
404
  if (typeof navigator !== "undefined" && navigator.userAgent) return `browser-${navigator.userAgent}`;
@@ -500,7 +506,7 @@ const loadEnv = (options) => {
500
506
  };
501
507
  const logger$1$1 = initializeLogger$1();
502
508
  const DEFAULT_DATASET_PULL_LIMIT = 100;
503
- const DEFAULT_DATASET_PUSH_BATCH_SIZE = 100;
509
+ const DEFAULT_DATASET_PUSH_BATCH_SIZE$1 = 100;
504
510
  var DatasetsResource = class extends BaseResource {
505
511
  constructor(baseHttpUrl, projectApiKey) {
506
512
  super(baseHttpUrl, projectApiKey);
@@ -544,7 +550,7 @@ var DatasetsResource = class extends BaseResource {
544
550
  * @param {boolean} [options.createDataset] - Whether to create the dataset if it doesn't exist
545
551
  * @returns {Promise<PushDatapointsResponse | undefined>}
546
552
  */
547
- async push({ points, name, id, batchSize = DEFAULT_DATASET_PUSH_BATCH_SIZE, createDataset = false }) {
553
+ async push({ points, name, id, batchSize = DEFAULT_DATASET_PUSH_BATCH_SIZE$1, createDataset = false }) {
548
554
  if (!name && !id) throw new Error("Either name or id must be provided");
549
555
  if (name && id) throw new Error("Only one of name or id must be provided");
550
556
  if (createDataset && !name) throw new Error("Name must be provided when creating a new dataset");
@@ -601,7 +607,7 @@ var DatasetsResource = class extends BaseResource {
601
607
  return response.json();
602
608
  }
603
609
  };
604
- const logger$3 = initializeLogger$1();
610
+ const logger$6 = initializeLogger$1();
605
611
  const INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH = 16e6;
606
612
  var EvalsResource = class extends BaseResource {
607
613
  constructor(baseHttpUrl, projectApiKey) {
@@ -737,7 +743,7 @@ var EvalsResource = class extends BaseResource {
737
743
  * @returns {Promise<GetDatapointsResponse>} Response from the datapoint retrieval
738
744
  */
739
745
  async getDatapoints({ datasetName, offset, limit }) {
740
- logger$3.warn("evals.getDatapoints() is deprecated. Use client.datasets.pull() instead.");
746
+ logger$6.warn("evals.getDatapoints() is deprecated. Use client.datasets.pull() instead.");
741
747
  const params = new URLSearchParams({
742
748
  name: datasetName,
743
749
  offset: offset.toString(),
@@ -754,7 +760,7 @@ var EvalsResource = class extends BaseResource {
754
760
  let length = initialLength;
755
761
  let lastResponse = null;
756
762
  for (let i = 0; i < maxRetries; i++) {
757
- logger$3.debug(`Retrying save datapoints... ${i + 1} of ${maxRetries}, length: ${length}`);
763
+ logger$6.debug(`Retrying save datapoints... ${i + 1} of ${maxRetries}, length: ${length}`);
758
764
  const response = await fetch(this.baseHttpUrl + `/v1/evals/${evalId}/datapoints`, {
759
765
  method: "POST",
760
766
  headers: this.headers(),
@@ -1012,6 +1018,410 @@ var LaminarClient = class {
1012
1018
  }
1013
1019
  };
1014
1020
 
1021
+ //#endregion
1022
+ //#region src/utils/logger.ts
1023
+ function initializeLogger(options) {
1024
+ const colorize = options?.colorize ?? true;
1025
+ const level = options?.level ?? process.env.LMNR_LOG_LEVEL?.toLowerCase()?.trim() ?? "info";
1026
+ return (0, pino.default)({ level }, (0, pino_pretty.PinoPretty)({
1027
+ colorize,
1028
+ minimumLevel: level,
1029
+ destination: 2
1030
+ }));
1031
+ }
1032
+
1033
+ //#endregion
1034
+ //#region src/utils/file.ts
1035
+ const logger$5 = initializeLogger();
1036
+ /**
1037
+ * Check if a file has a supported extension.
1038
+ */
1039
+ const isSupportedFile = (file) => {
1040
+ const ext = path.extname(file).toLowerCase();
1041
+ return [
1042
+ ".json",
1043
+ ".csv",
1044
+ ".jsonl"
1045
+ ].includes(ext);
1046
+ };
1047
+ /**
1048
+ * Collect all supported files from the given paths.
1049
+ * Handles both files and directories.
1050
+ */
1051
+ const collectFiles = async (paths, recursive = false) => {
1052
+ const collectedFiles = [];
1053
+ for (const filepath of paths) try {
1054
+ const stats = await fs_promises.stat(filepath);
1055
+ if (stats.isFile()) if (isSupportedFile(filepath)) collectedFiles.push(filepath);
1056
+ else logger$5.warn(`Skipping unsupported file type: ${filepath}`);
1057
+ else if (stats.isDirectory()) {
1058
+ const entries = await fs_promises.readdir(filepath);
1059
+ for (const entry of entries) {
1060
+ const fullPath = path.join(filepath, entry);
1061
+ const entryStats = await fs_promises.stat(fullPath);
1062
+ if (entryStats.isFile() && isSupportedFile(fullPath)) collectedFiles.push(fullPath);
1063
+ else if (recursive && entryStats.isDirectory()) {
1064
+ const subFiles = await collectFiles([fullPath], true);
1065
+ collectedFiles.push(...subFiles);
1066
+ }
1067
+ }
1068
+ }
1069
+ } catch (error) {
1070
+ logger$5.warn(`Path does not exist or is not accessible: ${filepath}. Error: ${error instanceof Error ? error.message : String(error)}`);
1071
+ }
1072
+ return collectedFiles;
1073
+ };
1074
+ /**
1075
+ * Read a JSON file and return its contents.
1076
+ */
1077
+ const readJsonFile = async (filepath) => {
1078
+ const content = await fs_promises.readFile(filepath, "utf-8");
1079
+ const parsed = JSON.parse(content);
1080
+ return Array.isArray(parsed) ? parsed : [parsed];
1081
+ };
1082
+ /**
1083
+ * Try to parse a string as JSON. If it fails, return the original string.
1084
+ */
1085
+ const tryParseJson = (content) => {
1086
+ if (typeof content !== "string") return content;
1087
+ const trimmed = content.trim();
1088
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return content;
1089
+ try {
1090
+ return JSON.parse(content);
1091
+ } catch (error) {
1092
+ logger$5.debug(`Error parsing JSON: ${error instanceof Error ? error.message : String(error)}`);
1093
+ return content;
1094
+ }
1095
+ };
1096
+ /**
1097
+ * Parse each field in a CSV row, attempting to convert JSON strings back to objects.
1098
+ */
1099
+ const parseCsvRow = (row) => {
1100
+ const parsed = {};
1101
+ for (const [key, value] of Object.entries(row)) parsed[key] = tryParseJson(value);
1102
+ return parsed;
1103
+ };
1104
+ /**
1105
+ * Read a CSV file and return its contents as an array of objects.
1106
+ */
1107
+ const readCsvFile = async (filepath) => new Promise((resolve, reject) => {
1108
+ const results = [];
1109
+ (0, fs.createReadStream)(filepath).pipe((0, csv_parser.default)()).on("data", (data) => results.push(parseCsvRow(data))).on("end", () => resolve(results)).on("error", reject);
1110
+ });
1111
+ /**
1112
+ * Read a JSONL file and return its contents as an array of objects.
1113
+ */
1114
+ async function readJsonlFile(filepath) {
1115
+ return (await fs_promises.readFile(filepath, "utf-8")).split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
1116
+ }
1117
+ /**
1118
+ * Read a single file and return its contents.
1119
+ */
1120
+ async function readFile(filepath) {
1121
+ const ext = path.extname(filepath).toLowerCase();
1122
+ if (ext === ".json") return readJsonFile(filepath);
1123
+ else if (ext === ".csv") return readCsvFile(filepath);
1124
+ else if (ext === ".jsonl") return readJsonlFile(filepath);
1125
+ else throw new Error(`Unsupported file type: ${ext}`);
1126
+ }
1127
+ /**
1128
+ * Load data from all files in the specified paths.
1129
+ */
1130
+ const loadFromPaths = async (paths, recursive = false) => {
1131
+ const files = await collectFiles(paths, recursive);
1132
+ if (files.length === 0) {
1133
+ logger$5.warn("No supported files found in the specified paths");
1134
+ return [];
1135
+ }
1136
+ logger$5.info(`Found ${files.length} file(s) to read`);
1137
+ const result = [];
1138
+ for (const file of files) try {
1139
+ const data = await readFile(file);
1140
+ result.push(...data);
1141
+ logger$5.info(`Read ${data.length} record(s) from ${file}`);
1142
+ } catch (error) {
1143
+ logger$5.error(`Error reading file ${file}: ${error instanceof Error ? error.message : String(error)}`);
1144
+ throw error;
1145
+ }
1146
+ return result;
1147
+ };
1148
+ /**
1149
+ * Write data to a JSON file.
1150
+ */
1151
+ const writeJsonFile = async (filepath, data) => {
1152
+ const content = JSON.stringify(data, null, 2);
1153
+ await fs_promises.writeFile(filepath, content, "utf-8");
1154
+ };
1155
+ /**
1156
+ * Format data as a CSV string.
1157
+ */
1158
+ const formatCsv = (data) => {
1159
+ const formattedData = data.map((item) => Object.fromEntries(Object.entries(item).map(([key, value]) => [key, stringifyForCsv(value)])));
1160
+ return (0, export_to_csv.asString)((0, export_to_csv.generateCsv)((0, export_to_csv.mkConfig)({ useKeysAsHeaders: true }))(formattedData));
1161
+ };
1162
+ /**
1163
+ * Write data to a CSV file.
1164
+ */
1165
+ const writeCsvFile = async (filepath, data) => {
1166
+ if (data.length === 0) throw new Error("No data to write to CSV");
1167
+ await fs_promises.writeFile(filepath, formatCsv(data), "utf-8");
1168
+ };
1169
+ /**
1170
+ * Write data to a JSONL file.
1171
+ */
1172
+ const writeJsonlFile = async (filepath, data) => {
1173
+ const lines = data.map((item) => JSON.stringify(item)).join("\n");
1174
+ await fs_promises.writeFile(filepath, lines + "\n", "utf-8");
1175
+ };
1176
+ /**
1177
+ * Write data to a file based on the file extension.
1178
+ */
1179
+ const writeToFile = async (filepath, data, format) => {
1180
+ const dir = path.dirname(filepath);
1181
+ await fs_promises.mkdir(dir, { recursive: true });
1182
+ const ext = format ?? path.extname(filepath).slice(1);
1183
+ if (format && format !== path.extname(filepath).slice(1)) logger$5.warn(`Output format ${format} does not match file extension ${path.extname(filepath).slice(1)}`);
1184
+ if (ext === "json") await writeJsonFile(filepath, data);
1185
+ else if (ext === "csv") await writeCsvFile(filepath, data);
1186
+ else if (ext === "jsonl") await writeJsonlFile(filepath, data);
1187
+ else throw new Error(`Unsupported output format: ${ext}`);
1188
+ };
1189
+ /**
1190
+ * Convert a value to a CSV-safe string.
1191
+ * - Strings and numbers pass through
1192
+ * - null/undefined become empty strings
1193
+ * - Objects and arrays are stringified to JSON
1194
+ */
1195
+ const stringifyForCsv = (value) => {
1196
+ if (value === null || value === void 0) return "";
1197
+ if (typeof value === "string") return value;
1198
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1199
+ return JSON.stringify(value);
1200
+ };
1201
+ /**
1202
+ * Print data to console in the specified format.
1203
+ */
1204
+ const printToConsole = (data, format = "json") => {
1205
+ if (format === "json") console.log(JSON.stringify(data, null, 2));
1206
+ else if (format === "csv") {
1207
+ if (data.length === 0) {
1208
+ logger$5.error("No data to print");
1209
+ return;
1210
+ }
1211
+ console.log(formatCsv(data));
1212
+ } else if (format === "jsonl") data.forEach((item) => console.log(JSON.stringify(item)));
1213
+ else throw new Error(`Unsupported output format: ${String(format)}. (supported formats: json, csv, jsonl)`);
1214
+ };
1215
+
1216
+ //#endregion
1217
+ //#region src/utils/output.ts
1218
+ /**
1219
+ * Write structured JSON to stdout. Use this for machine-readable output
1220
+ * when --json is set.
1221
+ */
1222
+ function outputJson(data) {
1223
+ console.log(JSON.stringify(data));
1224
+ }
1225
+ /**
1226
+ * Write a JSON error to stdout and exit with code 1.
1227
+ * Use this in --json mode so agents can parse the failure.
1228
+ */
1229
+ function outputJsonError(error, exitCode = 1) {
1230
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
1231
+ process.exit(exitCode);
1232
+ }
1233
+
1234
+ //#endregion
1235
+ //#region src/commands/dataset/index.ts
1236
+ const logger$4 = initializeLogger();
1237
+ const DEFAULT_DATASET_PULL_BATCH_SIZE = 100;
1238
+ const DEFAULT_DATASET_PUSH_BATCH_SIZE = 100;
1239
+ /**
1240
+ * Pull all data from a dataset in batches.
1241
+ */
1242
+ const pullAllData = async (client, identifier, batchSize = DEFAULT_DATASET_PULL_BATCH_SIZE, offset = 0, limit) => {
1243
+ let hasMore = true;
1244
+ let currentOffset = offset;
1245
+ const stopAt = limit !== void 0 ? offset + limit : void 0;
1246
+ const result = [];
1247
+ while (hasMore && (stopAt === void 0 || currentOffset < stopAt)) {
1248
+ const data = await client.datasets.pull({
1249
+ ...identifier,
1250
+ offset: currentOffset,
1251
+ limit: batchSize
1252
+ });
1253
+ result.push(...data.items);
1254
+ if (data.items.length === 0 || data.items.length < batchSize) hasMore = false;
1255
+ else if (stopAt !== void 0 && currentOffset + batchSize >= stopAt) hasMore = false;
1256
+ else if (data.totalCount !== void 0 && currentOffset + batchSize >= data.totalCount) hasMore = false;
1257
+ currentOffset += batchSize;
1258
+ }
1259
+ if (limit !== void 0) return result.slice(0, limit);
1260
+ return result;
1261
+ };
1262
+ /**
1263
+ * Handle datasets list command.
1264
+ */
1265
+ const handleDatasetsList = async (options) => {
1266
+ const client = new LaminarClient({
1267
+ projectApiKey: options.projectApiKey,
1268
+ baseUrl: options.baseUrl,
1269
+ port: options.port
1270
+ });
1271
+ try {
1272
+ const datasets = await client.datasets.listDatasets();
1273
+ if (options.json) {
1274
+ outputJson(datasets);
1275
+ return;
1276
+ }
1277
+ if (datasets.length === 0) {
1278
+ console.log("No datasets found.");
1279
+ return;
1280
+ }
1281
+ const idWidth = 36;
1282
+ const createdAtWidth = 19;
1283
+ console.log(`\n${"ID".padEnd(idWidth)} ${"Created At".padEnd(createdAtWidth)} Name`);
1284
+ console.log(`${"-".repeat(idWidth)} ${"-".repeat(createdAtWidth)} ${"-".repeat(20)}`);
1285
+ for (const dataset of datasets) {
1286
+ const createdAtStr = new Date(dataset.createdAt).toISOString().replace("T", " ").substring(0, 19);
1287
+ console.log(`${dataset.id.padEnd(idWidth)} ${createdAtStr.padEnd(createdAtWidth)} ${dataset.name}`);
1288
+ }
1289
+ console.log(`\nTotal: ${datasets.length} dataset(s)\n`);
1290
+ } catch (error) {
1291
+ if (options.json) outputJsonError(error);
1292
+ logger$4.error(`Failed to list datasets: ${error instanceof Error ? error.message : String(error)}`);
1293
+ process.exit(1);
1294
+ }
1295
+ };
1296
+ /**
1297
+ * Handle datasets push command.
1298
+ */
1299
+ const handleDatasetsPush = async (paths, options) => {
1300
+ if (!options.name && !options.id) {
1301
+ if (options.json) outputJsonError("Either name or id must be provided");
1302
+ logger$4.error("Either name or id must be provided");
1303
+ process.exit(1);
1304
+ }
1305
+ if (options.name && options.id) {
1306
+ if (options.json) outputJsonError("Only one of name or id must be provided");
1307
+ logger$4.error("Only one of name or id must be provided");
1308
+ process.exit(1);
1309
+ }
1310
+ const client = new LaminarClient({
1311
+ projectApiKey: options.projectApiKey,
1312
+ baseUrl: options.baseUrl,
1313
+ port: options.port
1314
+ });
1315
+ try {
1316
+ const data = await loadFromPaths(paths, options.recursive);
1317
+ if (data.length === 0) {
1318
+ if (options.json) outputJsonError("No data to push");
1319
+ logger$4.error("No data to push. Skipping");
1320
+ process.exit(1);
1321
+ }
1322
+ const identifier = options.name ? { name: options.name } : { id: options.id };
1323
+ const result = await client.datasets.push({
1324
+ points: data,
1325
+ ...identifier,
1326
+ batchSize: options.batchSize ?? DEFAULT_DATASET_PUSH_BATCH_SIZE
1327
+ });
1328
+ if (options.json) {
1329
+ outputJson({
1330
+ datasetId: result?.datasetId,
1331
+ count: data.length
1332
+ });
1333
+ return;
1334
+ }
1335
+ logger$4.info(`Pushed ${data.length} data points to dataset ${options.name || options.id}`);
1336
+ } catch (error) {
1337
+ if (options.json) outputJsonError(error);
1338
+ logger$4.error(`Failed to push dataset: ${error instanceof Error ? error.message : String(error)}`);
1339
+ process.exit(1);
1340
+ }
1341
+ };
1342
+ /**
1343
+ * Handle datasets pull command.
1344
+ */
1345
+ const handleDatasetsPull = async (outputPath, options) => {
1346
+ if (!options.name && !options.id) {
1347
+ if (options.json) outputJsonError("Either name or id must be provided");
1348
+ logger$4.error("Either name or id must be provided");
1349
+ process.exit(1);
1350
+ }
1351
+ if (options.name && options.id) {
1352
+ if (options.json) outputJsonError("Only one of name or id must be provided");
1353
+ logger$4.error("Only one of name or id must be provided");
1354
+ process.exit(1);
1355
+ }
1356
+ const client = new LaminarClient({
1357
+ projectApiKey: options.projectApiKey,
1358
+ baseUrl: options.baseUrl,
1359
+ port: options.port
1360
+ });
1361
+ const identifier = options.name ? { name: options.name } : { id: options.id };
1362
+ try {
1363
+ const result = await pullAllData(client, identifier, options.batchSize ?? DEFAULT_DATASET_PULL_BATCH_SIZE, options.offset ?? 0, options.limit);
1364
+ if (outputPath) {
1365
+ await writeToFile(outputPath, result, options.outputFormat);
1366
+ if (options.json) outputJson({
1367
+ path: outputPath,
1368
+ count: result.length
1369
+ });
1370
+ else logger$4.info(`Successfully pulled ${result.length} data points to ${outputPath}`);
1371
+ } else if (options.json) outputJson(result);
1372
+ else printToConsole(result, options.outputFormat ?? "json");
1373
+ } catch (error) {
1374
+ if (options.json) outputJsonError(error);
1375
+ logger$4.error(`Failed to pull dataset: ${error instanceof Error ? error.message : String(error)}`);
1376
+ process.exit(1);
1377
+ }
1378
+ };
1379
+ /**
1380
+ * Handle datasets create command.
1381
+ */
1382
+ const handleDatasetsCreate = async (name, paths, options) => {
1383
+ const client = new LaminarClient({
1384
+ projectApiKey: options.projectApiKey,
1385
+ baseUrl: options.baseUrl,
1386
+ port: options.port
1387
+ });
1388
+ try {
1389
+ const data = await loadFromPaths(paths, options.recursive);
1390
+ if (data.length === 0) {
1391
+ if (options.json) outputJsonError("No data to push");
1392
+ logger$4.error("No data to push. Skipping");
1393
+ process.exit(1);
1394
+ }
1395
+ logger$4.info(`Pushing ${data.length} data points to dataset '${name}'...`);
1396
+ await client.datasets.push({
1397
+ points: data,
1398
+ name,
1399
+ batchSize: options.batchSize ?? DEFAULT_DATASET_PUSH_BATCH_SIZE,
1400
+ createDataset: true
1401
+ });
1402
+ logger$4.info(`Successfully pushed ${data.length} data points to dataset '${name}'`);
1403
+ } catch (error) {
1404
+ if (options.json) outputJsonError(error);
1405
+ logger$4.error(`Failed to create dataset: ${error instanceof Error ? error.message : String(error)}`);
1406
+ process.exit(1);
1407
+ }
1408
+ logger$4.info(`Pulling data from dataset '${name}'...`);
1409
+ try {
1410
+ const result = await pullAllData(client, { name }, options.batchSize ?? DEFAULT_DATASET_PULL_BATCH_SIZE, 0, void 0);
1411
+ await writeToFile(options.outputFile, result, options.outputFormat);
1412
+ if (options.json) outputJson({
1413
+ name,
1414
+ path: options.outputFile,
1415
+ count: result.length
1416
+ });
1417
+ else logger$4.info(`Successfully created dataset '${name}' and saved ${result.length} datapoints to ${options.outputFile}`);
1418
+ } catch (error) {
1419
+ if (options.json) outputJsonError(error);
1420
+ logger$4.error(`Failed to pull dataset after creation: ${error instanceof Error ? error.message : String(error)}`);
1421
+ process.exit(1);
1422
+ }
1423
+ };
1424
+
1015
1425
  //#endregion
1016
1426
  //#region src/cache-server.ts
1017
1427
  const DEFAULT_START_PORT = 35667;
@@ -1301,20 +1711,9 @@ function createSSEClient(options) {
1301
1711
  */
1302
1712
  const WORKER_MESSAGE_PREFIX = "__LMNR_WORKER__:";
1303
1713
 
1304
- //#endregion
1305
- //#region src/utils.ts
1306
- function initializeLogger(options) {
1307
- const colorize = options?.colorize ?? true;
1308
- const level = options?.level ?? process.env.LMNR_LOG_LEVEL?.toLowerCase()?.trim() ?? "info";
1309
- return (0, pino.default)({ level }, (0, pino_pretty.PinoPretty)({
1310
- colorize,
1311
- minimumLevel: level
1312
- }));
1313
- }
1314
-
1315
1714
  //#endregion
1316
1715
  //#region src/subprocess/executor.ts
1317
- const logger$2 = initializeLogger();
1716
+ const logger$3 = initializeLogger();
1318
1717
  /**
1319
1718
  * Track and kill the currently running subprocess
1320
1719
  */
@@ -1345,16 +1744,16 @@ var SubprocessManager = class {
1345
1744
  const message = JSON.parse(messageJson);
1346
1745
  switch (message.type) {
1347
1746
  case "log":
1348
- logger$2[message.level](message.message);
1747
+ logger$3[message.level](message.message);
1349
1748
  break;
1350
1749
  case "error":
1351
1750
  hasError = true;
1352
- logger$2.error(`Worker error: ${message.error}`);
1353
- if (message.stack) logger$2.error(message.stack);
1751
+ logger$3.error(`Worker error: ${message.error}`);
1752
+ if (message.stack) logger$3.error(message.stack);
1354
1753
  break;
1355
1754
  }
1356
1755
  } catch {
1357
- logger$2.debug("Failed to parse worker protocol message. Printing raw line");
1756
+ logger$3.debug("Failed to parse worker protocol message. Printing raw line");
1358
1757
  console.log(line.substring(WORKER_MESSAGE_PREFIX.length));
1359
1758
  }
1360
1759
  else console.log(line);
@@ -1367,7 +1766,7 @@ var SubprocessManager = class {
1367
1766
  if (signal) reject(/* @__PURE__ */ new Error(`Worker terminated by signal: ${signal}`));
1368
1767
  else if (code === 0) resolve(result);
1369
1768
  else {
1370
- if (!hasError) logger$2.error(`Worker exited with code ${code}`);
1769
+ if (!hasError) logger$3.error(`Worker exited with code ${code}`);
1371
1770
  reject(/* @__PURE__ */ new Error(`Worker exited with code ${code}`));
1372
1771
  }
1373
1772
  });
@@ -1389,7 +1788,7 @@ var SubprocessManager = class {
1389
1788
  this.currentProcess.kill("SIGTERM");
1390
1789
  setTimeout(() => {
1391
1790
  if (processToKill && processToKill.exitCode === null) {
1392
- logger$2.warn("Child process did not terminate, using SIGKILL");
1791
+ logger$3.warn("Child process did not terminate, using SIGKILL");
1393
1792
  processToKill.kill("SIGKILL");
1394
1793
  }
1395
1794
  }, 5e3);
@@ -1484,7 +1883,7 @@ function getWorkerCommand(filePath, options) {
1484
1883
 
1485
1884
  //#endregion
1486
1885
  //#region src/commands/dev/metadata.ts
1487
- const logger$1 = initializeLogger();
1886
+ const logger$2 = initializeLogger();
1488
1887
  const TS_JS_EXTENSIONS = [
1489
1888
  ".ts",
1490
1889
  ".tsx",
@@ -1502,7 +1901,7 @@ const EXTENSIONS_TO_DISCOVER_METADATA = [...TS_JS_EXTENSIONS, ".py"];
1502
1901
  */
1503
1902
  const METADATA_PROTOCOL_PREFIX = "LMNR_METADATA:";
1504
1903
  const logLmnrPackageNotFoundAndExit = () => {
1505
- logger$1.error("@lmnr-ai/lmnr package not found or outdated. For JS/TS projects, please install the latest version of @lmnr-ai/lmnr in your project: npm install @lmnr-ai/lmnr\nYou might need to run `lmnr-cli` from the root of your project");
1904
+ logger$2.error("@lmnr-ai/lmnr package not found or outdated. For JS/TS projects, please install the latest version of @lmnr-ai/lmnr in your project: npm install @lmnr-ai/lmnr\nYou might need to run `lmnr-cli` from the root of your project");
1506
1905
  process.exit(1);
1507
1906
  };
1508
1907
  /**
@@ -1532,20 +1931,20 @@ const discoverTypeScriptMetadata = async (filePath, options) => {
1532
1931
  loadModule = buildModule.loadModule;
1533
1932
  selectRolloutFunction = buildModule.selectRolloutFunction;
1534
1933
  if (!extractRolloutFunctions || !buildFile || !loadModule || !selectRolloutFunction) {
1535
- logger$1.error("Missing exports from @lmnr-ai/lmnr modules. This may indicate an outdated package version.");
1934
+ logger$2.error("Missing exports from @lmnr-ai/lmnr modules. This may indicate an outdated package version.");
1536
1935
  logLmnrPackageNotFoundAndExit();
1537
1936
  }
1538
1937
  } catch (error) {
1539
1938
  if (error.code === "MODULE_NOT_FOUND") logLmnrPackageNotFoundAndExit();
1540
- logger$1.error(`Unexpected error loading @lmnr-ai/lmnr modules: ${error.message}`);
1939
+ logger$2.error(`Unexpected error loading @lmnr-ai/lmnr modules: ${error.message}`);
1541
1940
  throw error;
1542
1941
  }
1543
1942
  let paramsMetadata;
1544
1943
  try {
1545
1944
  paramsMetadata = extractRolloutFunctions(filePath);
1546
- logger$1.debug(`Extracted TypeScript metadata for ${paramsMetadata?.size} functions`);
1945
+ logger$2.debug(`Extracted TypeScript metadata for ${paramsMetadata?.size} functions`);
1547
1946
  } catch (error) {
1548
- logger$1.warn("Failed to extract TypeScript metadata, falling back to runtime parsing: " + (error instanceof Error ? error.message : String(error)));
1947
+ logger$2.warn("Failed to extract TypeScript metadata, falling back to runtime parsing: " + (error instanceof Error ? error.message : String(error)));
1549
1948
  }
1550
1949
  const moduleText = await buildFile(filePath, {
1551
1950
  externalPackages: options.externalPackages,
@@ -1557,21 +1956,21 @@ const discoverTypeScriptMetadata = async (filePath, options) => {
1557
1956
  });
1558
1957
  const selectedFunction = selectRolloutFunction(options.function);
1559
1958
  if (paramsMetadata) {
1560
- logger$1.debug(`Available TS metadata keys: ${Array.from(paramsMetadata.keys()).join(", ")}`);
1561
- logger$1.debug(`Looking for span name: ${selectedFunction.name} (runtime key: ${selectedFunction.exportName})`);
1959
+ logger$2.debug(`Available TS metadata keys: ${Array.from(paramsMetadata.keys()).join(", ")}`);
1960
+ logger$2.debug(`Looking for span name: ${selectedFunction.name} (runtime key: ${selectedFunction.exportName})`);
1562
1961
  let foundMetadata = null;
1563
1962
  for (const [exportName, metadata] of paramsMetadata.entries()) {
1564
- logger$1.debug(`Checking ${exportName}: span name = ${metadata.name}, export name = ${exportName}`);
1963
+ logger$2.debug(`Checking ${exportName}: span name = ${metadata.name}, export name = ${exportName}`);
1565
1964
  if (metadata.name === selectedFunction.name) {
1566
1965
  foundMetadata = metadata;
1567
- logger$1.debug(`Match. Export name: ${exportName}, span name: ${metadata.name}`);
1966
+ logger$2.debug(`Match. Export name: ${exportName}, span name: ${metadata.name}`);
1568
1967
  break;
1569
1968
  }
1570
1969
  }
1571
1970
  if (foundMetadata) {
1572
1971
  selectedFunction.params = foundMetadata.params;
1573
- logger$1.debug(`Using TypeScript metadata for span: ${selectedFunction.name}`);
1574
- } else logger$1.info(`No TypeScript metadata found for span name: ${selectedFunction.name}`);
1972
+ logger$2.debug(`Using TypeScript metadata for span: ${selectedFunction.name}`);
1973
+ } else logger$2.info(`No TypeScript metadata found for span name: ${selectedFunction.name}`);
1575
1974
  }
1576
1975
  return {
1577
1976
  functionName: selectedFunction.name,
@@ -1681,7 +2080,7 @@ const extractMetadataFromStdout = (stdout) => {
1681
2080
  * Discovers function metadata for Python files/modules by calling the lmnr Python CLI
1682
2081
  */
1683
2082
  const discoverPythonMetadata = async (filePathOrModule, options) => {
1684
- logger$1.debug(`Discovering Python metadata for ${filePathOrModule}`);
2083
+ logger$2.debug(`Discovering Python metadata for ${filePathOrModule}`);
1685
2084
  const args = ["discover"];
1686
2085
  if (options.pythonModule) args.push("--module", options.pythonModule);
1687
2086
  else args.push("--file", filePathOrModule);
@@ -1694,8 +2093,8 @@ const discoverPythonMetadata = async (filePathOrModule, options) => {
1694
2093
  };
1695
2094
  } catch (error) {
1696
2095
  const errorMessage = error instanceof Error ? error.message : String(error);
1697
- logger$1.error(`Error while loading Python file/module: ${errorMessage}`);
1698
- if (errorMessage.toLowerCase().includes("command not found") || errorMessage.includes("spawn lmnr ENOENT")) logger$1.info(`HINT: Make sure latest version of \`lmnr\` python package is installed. \`pip install --upgrade lmnr\`, or if you are running this command from a virtual environment, make sure to activate it. For \`uv\` users, rerun the command with \`uv run\`, e.g. "uv run npx lmnr-cli dev ${filePathOrModule}"`);
2096
+ logger$2.error(`Error while loading Python file/module: ${errorMessage}`);
2097
+ if (errorMessage.toLowerCase().includes("command not found") || errorMessage.includes("spawn lmnr ENOENT")) logger$2.info(`HINT: Make sure latest version of \`lmnr\` python package is installed. \`pip install --upgrade lmnr\`, or if you are running this command from a virtual environment, make sure to activate it. For \`uv\` users, rerun the command with \`uv run\`, e.g. "uv run npx lmnr-cli dev ${filePathOrModule}"`);
1699
2098
  throw error;
1700
2099
  }
1701
2100
  };
@@ -1707,7 +2106,7 @@ const discoverFunctionMetadata = async (filePathOrModule, options) => {
1707
2106
  const ext = path.extname(filePathOrModule);
1708
2107
  if (TS_JS_EXTENSIONS.includes(ext)) return await discoverTypeScriptMetadata(filePathOrModule, options);
1709
2108
  if (ext === ".py") return await discoverPythonMetadata(filePathOrModule, options);
1710
- logger$1.warn(`No metadata discovery available for ${ext} files`);
2109
+ logger$2.warn(`No metadata discovery available for ${ext} files`);
1711
2110
  return {
1712
2111
  functionName: options.function || path.basename(filePathOrModule, ext),
1713
2112
  params: []
@@ -1716,7 +2115,7 @@ const discoverFunctionMetadata = async (filePathOrModule, options) => {
1716
2115
 
1717
2116
  //#endregion
1718
2117
  //#region src/commands/dev/index.ts
1719
- const logger = initializeLogger();
2118
+ const logger$1 = initializeLogger();
1720
2119
  function newUUID() {
1721
2120
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
1722
2121
  return (0, uuid.v4)();
@@ -1747,7 +2146,7 @@ const tryParseArg = (arg) => {
1747
2146
  * Handles a run event from the backend
1748
2147
  */
1749
2148
  const handleRunEvent = async (event, sessionId, filePathOrModule, client, cacheServerPort, cache, setMetadata, options, subprocessManager) => {
1750
- logger.debug("Received run event");
2149
+ logger$1.debug("Received run event");
1751
2150
  const { trace_id, path_to_count, args: rawArgs, overrides } = event.data;
1752
2151
  const parsedArgs = Array.isArray(rawArgs) ? rawArgs.map(tryParseArg) : Object.fromEntries(Object.entries(rawArgs).map(([key, value]) => [key, tryParseArg(value)]));
1753
2152
  cache.clear();
@@ -1756,10 +2155,10 @@ const handleRunEvent = async (event, sessionId, filePathOrModule, client, cacheS
1756
2155
  overrides
1757
2156
  });
1758
2157
  try {
1759
- if (!trace_id || trace_id.trim() === "") logger.info("No spans in cache, starting fresh");
2158
+ if (!trace_id || trace_id.trim() === "") logger$1.info("No spans in cache, starting fresh");
1760
2159
  else {
1761
2160
  const paths = Object.keys(path_to_count || {});
1762
- if (paths.length === 0) logger.info("No spans to cache, starting fresh");
2161
+ if (paths.length === 0) logger$1.info("No spans to cache, starting fresh");
1763
2162
  else {
1764
2163
  const query = `
1765
2164
  SELECT name, input, output, attributes, path
@@ -1768,12 +2167,12 @@ const handleRunEvent = async (event, sessionId, filePathOrModule, client, cacheS
1768
2167
  AND path IN {paths:String[]}
1769
2168
  ORDER BY start_time ASC
1770
2169
  `;
1771
- logger.debug(`Querying spans from trace ${trace_id}...`);
2170
+ logger$1.debug(`Querying spans from trace ${trace_id}...`);
1772
2171
  const spans = await client.sql.query(query, {
1773
2172
  traceId: trace_id,
1774
2173
  paths
1775
2174
  });
1776
- logger.debug(`Received ${spans.length} spans from backend`);
2175
+ logger$1.debug(`Received ${spans.length} spans from backend`);
1777
2176
  const spansByPath = {};
1778
2177
  for (const span of spans) {
1779
2178
  const path$2 = span.path;
@@ -1784,12 +2183,14 @@ const handleRunEvent = async (event, sessionId, filePathOrModule, client, cacheS
1784
2183
  const maxCount = path_to_count?.[path$2] || 0;
1785
2184
  const spansToCache = pathSpans.slice(0, maxCount);
1786
2185
  spansToCache.forEach((span, index) => {
1787
- let parsedInput = span.input;
1788
- let parsedOutput = span.output;
1789
- let parsedAttributes = span.attributes;
2186
+ let parsedInput;
2187
+ let parsedOutput;
2188
+ let parsedAttributes;
1790
2189
  try {
1791
2190
  parsedInput = typeof span.input === "string" ? JSON.parse(span.input) : span.input;
1792
- } catch {}
2191
+ } catch {
2192
+ parsedInput = span.input;
2193
+ }
1793
2194
  try {
1794
2195
  parsedOutput = typeof span.output === "string" ? span.output : JSON.stringify(span.output);
1795
2196
  } catch {
@@ -1809,7 +2210,7 @@ const handleRunEvent = async (event, sessionId, filePathOrModule, client, cacheS
1809
2210
  const cacheKey = `${index}:${path$2}`;
1810
2211
  cache.set(cacheKey, cachedSpan);
1811
2212
  });
1812
- logger.info(`Cached ${spansToCache.length} spans for path: ${path$2}`);
2213
+ logger$1.info(`Cached ${spansToCache.length} spans for path: ${path$2}`);
1813
2214
  }
1814
2215
  setMetadata({
1815
2216
  pathToCount: path_to_count || {},
@@ -1848,7 +2249,7 @@ const handleRunEvent = async (event, sessionId, filePathOrModule, client, cacheS
1848
2249
  status: "RUNNING"
1849
2250
  });
1850
2251
  } catch (error) {
1851
- logger.error(`Error setting debugger session status: ${error instanceof Error ? error.message : error}`);
2252
+ logger$1.error(`Error setting debugger session status: ${error instanceof Error ? error.message : error}`);
1852
2253
  }
1853
2254
  await subprocessManager.execute({
1854
2255
  command: workerCommand.command,
@@ -1861,18 +2262,18 @@ const handleRunEvent = async (event, sessionId, filePathOrModule, client, cacheS
1861
2262
  status: "FINISHED"
1862
2263
  });
1863
2264
  } catch (error) {
1864
- logger.error(`Error setting debugger session status: ${error instanceof Error ? error.message : error}`);
2265
+ logger$1.error(`Error setting debugger session status: ${error instanceof Error ? error.message : error}`);
1865
2266
  }
1866
2267
  } catch (error) {
1867
- logger.error(`Error handling run event: ${error instanceof Error ? error.message : error}`);
1868
- if (error instanceof Error && error.stack) logger.error(error.stack);
2268
+ logger$1.error(`Error handling run event: ${error instanceof Error ? error.message : error}`);
2269
+ if (error instanceof Error && error.stack) logger$1.error(error.stack);
1869
2270
  try {
1870
2271
  await client.rolloutSessions.setStatus({
1871
2272
  sessionId,
1872
2273
  status: "FINISHED"
1873
2274
  });
1874
2275
  } catch (error$1) {
1875
- logger.error(`Error setting debugger session status: ${error$1 instanceof Error ? error$1.message : error$1}`);
2276
+ logger$1.error(`Error setting debugger session status: ${error$1 instanceof Error ? error$1.message : error$1}`);
1876
2277
  }
1877
2278
  }
1878
2279
  };
@@ -1889,30 +2290,30 @@ async function runDev(filePath, options = {}) {
1889
2290
  projectApiKey: options.projectApiKey,
1890
2291
  port: options.port
1891
2292
  });
1892
- logger.debug("Starting cache server...");
2293
+ logger$1.debug("Starting cache server...");
1893
2294
  const { port: cacheServerPort, server: cacheServer, cache, setMetadata } = await startCacheServer();
1894
- logger.debug(`Cache server started on port ${cacheServerPort}`);
2295
+ logger$1.debug(`Cache server started on port ${cacheServerPort}`);
1895
2296
  const subprocessManager = new SubprocessManager();
1896
2297
  let functionName = options.function;
1897
2298
  let params = [];
1898
2299
  try {
1899
2300
  if (isPythonModule || filePath && EXTENSIONS_TO_DISCOVER_METADATA.includes(path.extname(filePath))) {
1900
- logger.debug("Discovering entrypoint functions...");
2301
+ logger$1.debug("Discovering entrypoint functions...");
1901
2302
  const metadata = await discoverFunctionMetadata(filePathOrModule, options);
1902
2303
  functionName = metadata.functionName;
1903
2304
  params = metadata.params;
1904
- logger.info(`Serving function: ${functionName}`);
1905
- logger.debug(`Function parameters: ${JSON.stringify(params, null, 2)}`);
2305
+ logger$1.info(`Serving function: ${functionName}`);
2306
+ logger$1.debug(`Function parameters: ${JSON.stringify(params, null, 2)}`);
1906
2307
  } else if (filePath) {
1907
2308
  functionName = options.function || path.basename(filePath, path.extname(filePath));
1908
- logger.warn(`Metadata discovery not available for ${path.extname(filePath)} files`);
2309
+ logger$1.warn(`Metadata discovery not available for ${path.extname(filePath)} files`);
1909
2310
  }
1910
2311
  } catch (error) {
1911
- logger.error("Failed to discover entrypoint functions: " + (error instanceof Error ? error.message : String(error)));
2312
+ logger$1.error("Failed to discover entrypoint functions: " + (error instanceof Error ? error.message : String(error)));
1912
2313
  cacheServer.close();
1913
2314
  throw error;
1914
2315
  }
1915
- logger.debug("Setting up file watcher...");
2316
+ logger$1.debug("Setting up file watcher...");
1916
2317
  const watcher = chokidar.default.watch(".", {
1917
2318
  ignored: (path$2) => {
1918
2319
  const ignoredDirs = [
@@ -1947,7 +2348,7 @@ async function runDev(filePath, options = {}) {
1947
2348
  pollInterval: 100
1948
2349
  }
1949
2350
  });
1950
- logger.debug("Setting up SSE client...");
2351
+ logger$1.debug("Setting up SSE client...");
1951
2352
  let sseClient = null;
1952
2353
  try {
1953
2354
  sseClient = createSSEClient({
@@ -1959,45 +2360,45 @@ async function runDev(filePath, options = {}) {
1959
2360
  let currentRunPromise = null;
1960
2361
  let stopRequested = false;
1961
2362
  sseClient.on("heartbeat", () => {
1962
- logger.debug("Heartbeat received");
2363
+ logger$1.debug("Heartbeat received");
1963
2364
  });
1964
2365
  sseClient.on("run", (event) => {
1965
2366
  if (currentRunPromise !== null) {
1966
- logger.warn("Already processing a run event, skipping new run");
2367
+ logger$1.warn("Already processing a run event, skipping new run");
1967
2368
  return;
1968
2369
  }
1969
2370
  currentRunPromise = (async () => {
1970
2371
  try {
1971
2372
  stopRequested = false;
1972
2373
  if (reloadScheduled) {
1973
- logger.info("Reloading function metadata before run...");
2374
+ logger$1.info("Reloading function metadata before run...");
1974
2375
  if (isPythonModule || filePath && EXTENSIONS_TO_DISCOVER_METADATA.includes(path.extname(filePath))) try {
1975
2376
  const metadata = await discoverFunctionMetadata(filePathOrModule, options);
1976
2377
  if (stopRequested) {
1977
- logger.info("Run cancelled during metadata discovery");
2378
+ logger$1.info("Run cancelled during metadata discovery");
1978
2379
  return;
1979
2380
  }
1980
- logger.debug(`Updated function metadata: ${metadata.functionName}`);
1981
- logger.debug(`Updated parameters: ${JSON.stringify(metadata.params, null, 2)}`);
2381
+ logger$1.debug(`Updated function metadata: ${metadata.functionName}`);
2382
+ logger$1.debug(`Updated parameters: ${JSON.stringify(metadata.params, null, 2)}`);
1982
2383
  if (sseClient) {
1983
2384
  sseClient.updateMetadata(metadata.params, metadata.functionName);
1984
- logger.debug("Notified backend of metadata changes");
2385
+ logger$1.debug("Notified backend of metadata changes");
1985
2386
  }
1986
2387
  reloadScheduled = false;
1987
2388
  } catch (error) {
1988
- logger.error("Failed to update function metadata: " + (error instanceof Error ? error.message : String(error)));
1989
- if (error instanceof Error && error.stack) logger.debug(`Stack trace: ${error.stack}`);
2389
+ logger$1.error("Failed to update function metadata: " + (error instanceof Error ? error.message : String(error)));
2390
+ if (error instanceof Error && error.stack) logger$1.debug(`Stack trace: ${error.stack}`);
1990
2391
  return;
1991
2392
  }
1992
2393
  else reloadScheduled = false;
1993
2394
  }
1994
2395
  if (stopRequested) {
1995
- logger.info("Run cancelled before execution");
2396
+ logger$1.info("Run cancelled before execution");
1996
2397
  return;
1997
2398
  }
1998
2399
  await handleRunEvent(event, sessionId, filePathOrModule, client, cacheServerPort, cache, setMetadata, options, subprocessManager);
1999
2400
  } catch (error) {
2000
- logger.error("Unhandled error in run event handler: " + (error instanceof Error ? error.message : String(error)));
2401
+ logger$1.error("Unhandled error in run event handler: " + (error instanceof Error ? error.message : String(error)));
2001
2402
  } finally {
2002
2403
  currentRunPromise = null;
2003
2404
  }
@@ -2007,65 +2408,65 @@ async function runDev(filePath, options = {}) {
2007
2408
  const projectId = event.data.project_id;
2008
2409
  const sessionId$1 = event.data.session_id;
2009
2410
  const frontendUrl = getFrontendUrl(options.baseUrl, options.frontendPort);
2010
- if (!didLogHandshake) logger.info(`View your session at ${frontendUrl}/project/${projectId}/rollout-sessions/${sessionId$1}`);
2411
+ if (!didLogHandshake) logger$1.info(`View your session at ${frontendUrl}/project/${projectId}/debugger-sessions/${sessionId$1}`);
2011
2412
  didLogHandshake = true;
2012
2413
  });
2013
2414
  sseClient.on("error", (error) => {
2014
- logger.warn(`Error connecting to backend: ${error.message}`);
2415
+ logger$1.warn(`Error connecting to backend: ${error.message}`);
2015
2416
  });
2016
2417
  sseClient.on("reconnecting", () => {
2017
- logger.info("Reconnecting to backend...");
2418
+ logger$1.info("Reconnecting to backend...");
2018
2419
  });
2019
2420
  sseClient.on("heartbeat_timeout", () => {
2020
- logger.debug("Heartbeat timeout, reconnecting...");
2421
+ logger$1.debug("Heartbeat timeout, reconnecting...");
2021
2422
  });
2022
2423
  sseClient.on("stop", () => {
2023
- logger.debug("Stop event received");
2424
+ logger$1.debug("Stop event received");
2024
2425
  stopRequested = true;
2025
- if (subprocessManager.kill()) logger.info("Current run cancelled");
2426
+ if (subprocessManager.kill()) logger$1.info("Current run cancelled");
2026
2427
  });
2027
2428
  let reloadTimeout = null;
2028
2429
  let reloadScheduled = false;
2029
2430
  watcher.on("change", (changedPath) => {
2030
- logger.info(`File changed: ${changedPath}, scheduling reload...`);
2431
+ logger$1.info(`File changed: ${changedPath}, scheduling reload...`);
2031
2432
  if (reloadTimeout) clearTimeout(reloadTimeout);
2032
2433
  reloadTimeout = setTimeout(() => {
2033
- logger.debug("Marking reload as scheduled for next run...");
2434
+ logger$1.debug("Marking reload as scheduled for next run...");
2034
2435
  reloadTimeout = null;
2035
2436
  reloadScheduled = true;
2036
2437
  }, 100);
2037
2438
  });
2038
2439
  const shutdown = () => {
2039
- logger.debug("Shutting down...");
2440
+ logger$1.debug("Shutting down...");
2040
2441
  if (reloadTimeout) {
2041
2442
  clearTimeout(reloadTimeout);
2042
2443
  reloadTimeout = null;
2043
2444
  }
2044
2445
  reloadScheduled = false;
2045
- logger.debug("Closing file watcher...");
2446
+ logger$1.debug("Closing file watcher...");
2046
2447
  watcher.close().catch((error) => {
2047
- logger.error(`Failed to close file watcher: ${error instanceof Error ? error.message : error}`);
2448
+ logger$1.error(`Failed to close file watcher: ${error instanceof Error ? error.message : error}`);
2048
2449
  });
2049
2450
  subprocessManager.kill();
2050
- logger.debug("Deleting debugger session...");
2451
+ logger$1.debug("Deleting debugger session...");
2051
2452
  client.rolloutSessions.delete({ sessionId }).then(() => {
2052
2453
  if (sseClient) sseClient.shutdown();
2053
2454
  cacheServer.close(() => {
2054
- logger.debug("Cache server closed");
2455
+ logger$1.debug("Cache server closed");
2055
2456
  });
2056
2457
  process.exit(0);
2057
2458
  }).catch((error) => {
2058
- logger.warn(`Failed to delete debugger session: ${error instanceof Error ? error.message : error}`);
2459
+ logger$1.warn(`Failed to delete debugger session: ${error instanceof Error ? error.message : error}`);
2059
2460
  process.exit(1);
2060
2461
  });
2061
2462
  };
2062
2463
  process.on("SIGINT", shutdown);
2063
2464
  process.on("SIGTERM", shutdown);
2064
2465
  process.stdin.resume();
2065
- logger.debug("Connecting to backend...");
2466
+ logger$1.debug("Connecting to backend...");
2066
2467
  await sseClient.connectAndListen();
2067
2468
  } catch (error) {
2068
- logger.error("Failed to start dev command: " + (error instanceof Error ? error.message : String(error)));
2469
+ logger$1.error("Failed to start dev command: " + (error instanceof Error ? error.message : String(error)));
2069
2470
  try {
2070
2471
  await client.rolloutSessions.delete({ sessionId });
2071
2472
  } catch {}
@@ -2076,11 +2477,92 @@ async function runDev(filePath, options = {}) {
2076
2477
  }
2077
2478
  }
2078
2479
 
2480
+ //#endregion
2481
+ //#region src/commands/sql/index.ts
2482
+ const logger = initializeLogger();
2483
+ const handleSqlQuery = async (query, options) => {
2484
+ const client = new LaminarClient({
2485
+ projectApiKey: options.projectApiKey,
2486
+ baseUrl: options.baseUrl,
2487
+ port: options.port
2488
+ });
2489
+ try {
2490
+ const rows = await client.sql.query(query);
2491
+ if (options.json) {
2492
+ outputJson(rows);
2493
+ return;
2494
+ }
2495
+ if (rows.length === 0) {
2496
+ console.log("No rows returned.");
2497
+ return;
2498
+ }
2499
+ const columns = Object.keys(rows[0]);
2500
+ const widths = columns.map((col) => rows.reduce((max, r) => Math.max(max, String(r[col] ?? "").length), col.length));
2501
+ const header = columns.map((col, i) => col.padEnd(widths[i])).join(" ");
2502
+ const divider = widths.map((w) => "-".repeat(w)).join(" ");
2503
+ console.log(`\n${header}`);
2504
+ console.log(divider);
2505
+ for (const row of rows) console.log(columns.map((col, i) => String(row[col] ?? "").padEnd(widths[i])).join(" "));
2506
+ console.log(`\n${rows.length} row(s)\n`);
2507
+ } catch (error) {
2508
+ if (options.json) outputJsonError(error);
2509
+ logger.error(`Query failed: ${error instanceof Error ? error.message : String(error)}`);
2510
+ process.exit(1);
2511
+ }
2512
+ };
2513
+
2514
+ //#endregion
2515
+ //#region src/commands/sql/schema.ts
2516
+ const SQL_SCHEMA_HELP = `
2517
+ Available tables:
2518
+ spans
2519
+ span_id (UUID), name (String), span_type (String: DEFAULT|LLM|TOOL),
2520
+ start_time (DateTime64), end_time (DateTime64), duration (Float64),
2521
+ input_cost (Float64), output_cost (Float64), total_cost (Float64),
2522
+ input_tokens (Int64), output_tokens (Int64), total_tokens (Int64),
2523
+ request_model (String), response_model (String), model (String),
2524
+ trace_id (UUID), provider (String), path (String),
2525
+ input (String), output (String), status (String),
2526
+ parent_span_id (UUID), attributes (String), tags (Array(String))
2527
+
2528
+ traces
2529
+ id (UUID), start_time (DateTime64), end_time (DateTime64),
2530
+ input_tokens (Int64), output_tokens (Int64), total_tokens (Int64),
2531
+ input_cost (Float64), output_cost (Float64), total_cost (Float64),
2532
+ duration (Float64), metadata (String), session_id (String),
2533
+ user_id (String), status (String), top_span_id (UUID),
2534
+ top_span_name (String), top_span_type (String), trace_type (String),
2535
+ tags (Array(String)), has_browser_session (Bool)
2536
+
2537
+ events
2538
+ id (UUID), type (String), name (String), span_id (UUID),
2539
+ timestamp (DateTime64), attributes (String)
2540
+
2541
+ signal_events
2542
+ id (UUID), signal_id (UUID), trace_id (UUID), run_id (UUID),
2543
+ name (String), payload (String), timestamp (DateTime64)
2544
+
2545
+ signal_runs
2546
+ signal_id (UUID), job_id (UUID), trigger_id (UUID), run_id (UUID),
2547
+ trace_id (UUID), status (String), event_id (UUID), updated_at (DateTime64)
2548
+
2549
+ evaluation_datapoints
2550
+ id (UUID), evaluation_id (UUID), data (String), target (String),
2551
+ metadata (String), executor_output (String), index (UInt64),
2552
+ trace_id (UUID), group_id (String), scores (String),
2553
+ created_at (DateTime64), dataset_id (UUID),
2554
+ dataset_datapoint_id (UUID), dataset_datapoint_created_at (DateTime64)
2555
+
2556
+ dataset_datapoints
2557
+ id (UUID), created_at (DateTime64), dataset_id (UUID),
2558
+ data (String), target (String), metadata (String)
2559
+ `;
2560
+
2079
2561
  //#endregion
2080
2562
  //#region src/index.ts
2081
2563
  async function main() {
2082
2564
  const program = new commander.Command();
2083
- program.name("lmnr-cli").description("CLI for Laminar agent debugger").version(version$1, "-v, --version", "display version number");
2565
+ program.name("lmnr-cli").description("CLI for the Laminar agent observability platform").version(version$1, "-v, --version", "display version number");
2084
2566
  program.command("dev").description("Start a debugging session").argument("[file]", "Path to file containing the entrypoint function(s). Either `file` or `-m` must be provided.").option("-m, --python-module <module>", "Python module path (e.g., src.myfile). Either `file` or `-m` must be provided.").option("--function <name>", "Specific function to serve (if multiple entrypoint functions found)").option("--project-api-key <key>", "Project API key. If not provided, reads from LMNR_PROJECT_API_KEY env variable").option("--base-url <url>", "Base URL for the Laminar API. Defaults to https://api.lmnr.ai or LMNR_BASE_URL env variable").option("--port <port>", "Port for the Laminar API. Defaults to 443", (val) => parseInt(val, 10)).option("--grpc-port <port>", "Port for the Laminar gRPC backend. Defaults to 8443", (val) => parseInt(val, 10)).option("--frontend-port <port>", "Port for the Laminar frontend. Defaults to 5667", (val) => parseInt(val, 10)).option("--external-packages <packages...>", "[ADVANCED] List of packages to pass as external to esbuild. This will not link the packages directly into the dev file, but will instead require them at runtime. Read more: https://esbuild.github.io/api/#external").option("--dynamic-imports-to-skip <modules...>", "[ADVANCED] List of module names to skip when encountered as dynamic imports. These dynamic imports will resolve to an empty module to prevent build failures. This is meant to skip the imports that are not used in the entrypoint function itself.").option("--command <command>", "[ADVANCED] Custom command to run the worker (e.g., python3, node)").option("--command-args <args...>", "[ADVANCED] Arguments for the custom command").action(async (file, options) => {
2085
2567
  if (!file && !options.pythonModule) {
2086
2568
  console.error("Error: Must provide either a file path or --python-module (-m) flag");
@@ -2097,6 +2579,44 @@ Examples:
2097
2579
  $ lmnr-cli dev agent.py # Python file (script mode)
2098
2580
  $ lmnr-cli dev -m src.agent # Python module (module mode)
2099
2581
  $ lmnr-cli dev agent.ts --function myAgent # Specific function
2582
+ `);
2583
+ const datasetsCmd = program.command("dataset").description("Manage datasets").option("--project-api-key <key>", "Project API key. If not provided, reads from LMNR_PROJECT_API_KEY env variable").option("--base-url <url>", "Base URL for the Laminar API. Defaults to https://api.lmnr.ai or LMNR_BASE_URL env variable").option("--port <port>", "Port for the Laminar API. Defaults to 443", (val) => parseInt(val, 10)).option("--json", "Output structured JSON to stdout");
2584
+ datasetsCmd.command("list").description("List all datasets").action(async (_options, cmd) => {
2585
+ await handleDatasetsList(cmd.optsWithGlobals());
2586
+ });
2587
+ datasetsCmd.command("push").description("Push datapoints to an existing dataset").argument("<paths...>", "Paths to files or directories containing data to push").option("-n, --name <name>", "Name of the dataset (either name or id must be provided)").option("--id <id>", "ID of the dataset (either name or id must be provided)").option("-r, --recursive", "Recursively read files in directories", false).option("--batch-size <size>", "Batch size for pushing data", (val) => parseInt(val, 10), 100).action(async (paths, _options, cmd) => {
2588
+ await handleDatasetsPush(paths, cmd.optsWithGlobals());
2589
+ });
2590
+ datasetsCmd.command("pull").description("Pull data from a dataset").argument("[output-path]", "Path to save the data. If not provided, prints to console").option("-n, --name <name>", "Name of the dataset (either name or id must be provided)").option("--id <id>", "ID of the dataset (either name or id must be provided)").option("--output-format <format>", "Output format (json, csv, jsonl). Inferred from file extension if not provided").option("--batch-size <size>", "Batch size for pulling data", (val) => parseInt(val, 10), 100).option("--limit <limit>", "Limit number of datapoints to pull", (val) => parseInt(val, 10)).option("--offset <offset>", "Offset for pagination", (val) => parseInt(val, 10), 0).action(async (outputPath, _options, cmd) => {
2591
+ await handleDatasetsPull(outputPath, cmd.optsWithGlobals());
2592
+ });
2593
+ datasetsCmd.command("create").description("Create a dataset from input files").argument("<name>", "Name of the dataset to create").argument("<paths...>", "Paths to files or directories containing data to push").requiredOption("-o, --output-file <file>", "Path to save the pulled data").option("--output-format <format>", "Output format (json, csv, jsonl). Inferred from file extension if not provided").option("-r, --recursive", "Recursively read files in directories", false).option("--batch-size <size>", "Batch size for pushing/pulling data", (val) => parseInt(val, 10), 100).action(async (name, paths, _options, cmd) => {
2594
+ await handleDatasetsCreate(name, paths, cmd.optsWithGlobals());
2595
+ });
2596
+ const sqlCmd = program.command("sql").description("Run SQL queries against your Laminar project data").option("--project-api-key <key>", "Project API key. If not provided, reads from LMNR_PROJECT_API_KEY env variable").option("--base-url <url>", "Base URL for the Laminar API. Defaults to https://api.lmnr.ai or LMNR_BASE_URL env variable").option("--port <port>", "Port for the Laminar API. Defaults to 443", (val) => parseInt(val, 10)).option("--json", "Output structured JSON to stdout");
2597
+ sqlCmd.command("query").description("Execute a SQL query").argument("<query>", "SQL query string").action(async (query, _options, cmd) => {
2598
+ await handleSqlQuery(query, cmd.optsWithGlobals());
2599
+ }).addHelpText("after", SQL_SCHEMA_HELP + `
2600
+ Examples:
2601
+ $ lmnr-cli sql query "SELECT * FROM spans LIMIT 10"
2602
+ $ lmnr-cli sql query "SELECT id, total_cost, status FROM traces LIMIT 20"
2603
+ $ lmnr-cli sql query "SELECT * FROM spans LIMIT 10" --json
2604
+ `);
2605
+ sqlCmd.command("schema").description("Show available tables and their columns").action(() => {
2606
+ process.stdout.write(SQL_SCHEMA_HELP);
2607
+ });
2608
+ program.addHelpText("after", `
2609
+ Examples:
2610
+ lmnr-cli dev agent.ts # Debugger TypeScript entrypoint
2611
+ lmnr-cli dev agent.py # Debugger Python script mode
2612
+ lmnr-cli dev -m src.agent # Debuger Python module mode
2613
+ lmnr-cli dataset list --json # List all datasets
2614
+ lmnr-cli dataset push data.jsonl -n my-dataset --json # Push data to a dataset
2615
+ lmnr-cli dataset pull output.jsonl -n my-dataset --json # Pull data from a dataset
2616
+ lmnr-cli sql query "SELECT * FROM spans LIMIT 10" --json # Query spans
2617
+ lmnr-cli sql query "SELECT t.id, s.name FROM traces t \
2618
+ JOIN spans s ON t.id = s.trace_id LIMIT 20" --json
2619
+ lmnr-cli sql schema # Show available tables
2100
2620
  `);
2101
2621
  await program.parseAsync();
2102
2622
  }