@lexq/cli 0.1.7 → 0.1.9

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/AGENTS.md CHANGED
@@ -46,7 +46,7 @@ Detailed documentation lives in the `skills/` directory. **Read the relevant ski
46
46
  6. **Copy full UUIDs from output.** Never guess or truncate IDs.
47
47
  7. **Handle errors gracefully.** Check the error code and follow the action table in `lexq-shared/SKILL.md`.
48
48
 
49
- ## Complete Command Inventory (61 commands)
49
+ ## Complete Command Inventory (63 commands)
50
50
 
51
51
  ```
52
52
  lexq auth login|logout|whoami
@@ -59,6 +59,7 @@ lexq facts list|create|update|delete
59
59
  lexq deploy publish|live|rollback|undeploy|history|detail|overview
60
60
  lexq analytics dry-run|dry-run-compare|requirements
61
61
  lexq analytics simulation start|status|list|cancel|export
62
+ lexq analytics dataset upload|template
62
63
  lexq history list|get|stats
63
64
  lexq integrations list|get|save|delete|config-spec
64
65
  lexq logs list|get|action|bulk-action
package/README.md CHANGED
@@ -74,6 +74,7 @@ lexq facts list | create | update | delete
74
74
  lexq deploy publish | live | rollback | undeploy | history | detail | overview
75
75
  lexq analytics dry-run | dry-run-compare | requirements
76
76
  lexq analytics simulation start | status | list | cancel | export
77
+ lexq analytics dataset upload | template
77
78
  lexq history list | get | stats
78
79
  lexq integrations list | get | save | delete | config-spec
79
80
  lexq logs list | get | action | bulk-action
@@ -156,7 +157,7 @@ Run LexQ CLI as an MCP (Model Context Protocol) server for seamless AI agent int
156
157
  lexq serve --mcp
157
158
  ```
158
159
 
159
- This starts a stdio MCP server exposing 53 tools — the full LexQ API — to any MCP-compatible client.
160
+ This starts a stdio MCP server exposing 55 tools — the full LexQ API — to any MCP-compatible client.
160
161
 
161
162
  ### Claude Desktop
162
163
 
package/dist/index.js CHANGED
@@ -1200,7 +1200,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1200
1200
  }
1201
1201
 
1202
1202
  // src/commands/analytics.ts
1203
- import { readFileSync as readFileSync2 } from "fs";
1203
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1204
1204
  import "commander";
1205
1205
  function registerAnalyticsCommands(program) {
1206
1206
  const analytics = program.command("analytics").description("Dry run, simulation, and requirements");
@@ -1464,7 +1464,6 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1464
1464
  }
1465
1465
  );
1466
1466
  if (opts.output) {
1467
- const { writeFileSync: writeFileSync2 } = await import("fs");
1468
1467
  const text = typeof response === "string" ? response : JSON.stringify(response, null, 2);
1469
1468
  writeFileSync2(opts.output, text, "utf-8");
1470
1469
  console.log(`\u2713 Exported to ${opts.output}`);
@@ -1476,6 +1475,83 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1476
1475
  process.exit(1);
1477
1476
  }
1478
1477
  });
1478
+ const dataset = analytics.command("dataset").description("Upload datasets and download templates");
1479
+ dataset.command("upload").description("Upload a CSV or JSON file as a simulation dataset").requiredOption("--file <path>", "Path to CSV or JSON file").action(async (opts) => {
1480
+ try {
1481
+ const globalOpts = program.opts();
1482
+ const config = loadConfig();
1483
+ const baseUrl = globalOpts.baseUrl ?? config.baseUrl;
1484
+ const apiKey = globalOpts.apiKey ?? config.apiKey;
1485
+ if (!apiKey) {
1486
+ throw new Error('Not authenticated. Run "lexq auth login" first.');
1487
+ }
1488
+ const filePath = opts.file;
1489
+ const fileBuffer = readFileSync2(filePath);
1490
+ const fileName = filePath.split("/").pop() ?? "dataset";
1491
+ const ext = fileName.split(".").pop()?.toLowerCase();
1492
+ let contentType = "application/octet-stream";
1493
+ if (ext === "csv") contentType = "text/csv";
1494
+ else if (ext === "json") contentType = "application/json";
1495
+ const blob = new Blob([fileBuffer], { type: contentType });
1496
+ const formData = new FormData();
1497
+ formData.append("file", blob, fileName);
1498
+ const url = new URL("analytics/datasets/upload", baseUrl.endsWith("/") ? baseUrl : baseUrl + "/");
1499
+ if (globalOpts.verbose) {
1500
+ console.error(`\u2192 POST ${url.toString()}`);
1501
+ console.error(` File: ${filePath} (${fileBuffer.length} bytes)`);
1502
+ }
1503
+ const response = await fetch(url.toString(), {
1504
+ method: "POST",
1505
+ headers: { "X-API-KEY": apiKey },
1506
+ body: formData
1507
+ });
1508
+ if (!response.ok) {
1509
+ const errorText = await response.text();
1510
+ throw new Error(`Upload failed (${response.status}): ${errorText}`);
1511
+ }
1512
+ const envelope = await response.json();
1513
+ const result = envelope.data;
1514
+ console.log(`\u2713 Dataset uploaded: ${result.path}`);
1515
+ console.log(` File: ${result.filename} (${result.size} bytes)`);
1516
+ printJson(result);
1517
+ } catch (error) {
1518
+ printError(error);
1519
+ process.exit(1);
1520
+ }
1521
+ });
1522
+ dataset.command("template").description("Download a dataset template based on version requirements").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Policy version ID").option("--format <fmt>", "Template format: csv or json", "csv").option("--output <path>", "Output file path").action(async (opts) => {
1523
+ try {
1524
+ const globalOpts = program.opts();
1525
+ const config = loadConfig();
1526
+ const baseUrl = globalOpts.baseUrl ?? config.baseUrl;
1527
+ const apiKey = globalOpts.apiKey ?? config.apiKey;
1528
+ if (!apiKey) {
1529
+ throw new Error('Not authenticated. Run "lexq auth login" first.');
1530
+ }
1531
+ const fmt = opts.format === "json" ? "json" : "csv";
1532
+ const url = new URL(
1533
+ `analytics/groups/${opts.groupId}/versions/${opts.versionId}/dataset-template`,
1534
+ baseUrl.endsWith("/") ? baseUrl : baseUrl + "/"
1535
+ );
1536
+ url.searchParams.set("format", fmt);
1537
+ const response = await fetch(url.toString(), {
1538
+ headers: { "X-API-KEY": apiKey, Accept: "*/*" }
1539
+ });
1540
+ if (!response.ok) {
1541
+ throw new Error(`Template download failed (${response.status})`);
1542
+ }
1543
+ const text = await response.text();
1544
+ if (opts.output) {
1545
+ writeFileSync2(opts.output, text, "utf-8");
1546
+ console.log(`\u2713 Template saved to ${opts.output}`);
1547
+ } else {
1548
+ console.log(text);
1549
+ }
1550
+ } catch (error) {
1551
+ printError(error);
1552
+ process.exit(1);
1553
+ }
1554
+ });
1479
1555
  }
1480
1556
  function resolveBody(opts) {
1481
1557
  if (opts.file) {
@@ -1889,6 +1965,27 @@ function createCallApiFromConfig() {
1889
1965
  return async (method, path, opts) => {
1890
1966
  try {
1891
1967
  const config = loadConfig();
1968
+ if (opts?.upload) {
1969
+ const url = new URL(path, config.baseUrl.endsWith("/") ? config.baseUrl : config.baseUrl + "/");
1970
+ if (opts.params) {
1971
+ for (const [key, value] of Object.entries(opts.params)) {
1972
+ if (value !== void 0) url.searchParams.set(key, value);
1973
+ }
1974
+ }
1975
+ if (!config.apiKey) {
1976
+ throw new Error('Not authenticated. Run "lexq auth login" first.');
1977
+ }
1978
+ const blob = new Blob([opts.upload.content], { type: "text/plain" });
1979
+ const formData = new FormData();
1980
+ formData.append(opts.upload.fieldName, blob, opts.upload.filename);
1981
+ const response = await fetch(url.toString(), {
1982
+ method,
1983
+ headers: { "X-API-KEY": config.apiKey },
1984
+ body: formData
1985
+ });
1986
+ const data2 = await response.json();
1987
+ return { content: [{ type: "text", text: JSON.stringify(data2, null, 2) }] };
1988
+ }
1892
1989
  const clientOpts = {
1893
1990
  apiKey: config.apiKey,
1894
1991
  baseUrl: config.baseUrl
@@ -2612,6 +2709,47 @@ Example body:
2612
2709
  params: { format }
2613
2710
  })
2614
2711
  );
2712
+ server.registerTool(
2713
+ "lexq_dataset_upload",
2714
+ {
2715
+ title: "Upload Dataset",
2716
+ description: `Upload inline CSV or JSON content as a simulation dataset.
2717
+ The content is uploaded to S3 and a path is returned.
2718
+ Use this path in simulation start with dataset type UPLOADED.
2719
+
2720
+ CSV example:
2721
+ user_id,payment_amount
2722
+ user_001,150000
2723
+ user_002,50000
2724
+
2725
+ JSON example:
2726
+ [{"user_id":"user_001","payment_amount":150000}, {"user_id":"user_002","payment_amount":50000}]`,
2727
+ inputSchema: {
2728
+ content: z6.string().describe("CSV or JSON content as string"),
2729
+ filename: z6.string().default("dataset.csv").describe("Filename with extension (.csv or .json)")
2730
+ }
2731
+ },
2732
+ async ({ content, filename }) => callApi("POST", "analytics/datasets/upload", {
2733
+ upload: { content, filename, fieldName: "file" }
2734
+ })
2735
+ );
2736
+ server.registerTool(
2737
+ "lexq_dataset_template",
2738
+ {
2739
+ title: "Download Dataset Template",
2740
+ description: "Generate a sample CSV or JSON template based on the required facts of a version. Use this to understand the expected data format before uploading a dataset.",
2741
+ inputSchema: {
2742
+ groupId: z6.string().uuid().describe("Policy group ID"),
2743
+ versionId: z6.string().uuid().describe("Version ID"),
2744
+ format: z6.enum(["csv", "json"]).default("csv").describe("Template format")
2745
+ }
2746
+ },
2747
+ async ({ groupId, versionId, format }) => callApi(
2748
+ "GET",
2749
+ `analytics/groups/${groupId}/versions/${versionId}/dataset-template`,
2750
+ { params: { format } }
2751
+ )
2752
+ );
2615
2753
  }
2616
2754
 
2617
2755
  // src/mcp/tools/history.ts