@lexq/cli 0.1.18 → 0.1.20

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.
@@ -27,7 +27,7 @@ type CallApi = (method: string, path: string, opts?: {
27
27
  declare function paginationParams(page?: number, size?: number): Record<string, string>;
28
28
 
29
29
  /**
30
- * Registers all 55 MCP tools on the given server.
30
+ * Registers all 62 MCP tools on the given server.
31
31
  *
32
32
  * @param server - McpServer instance
33
33
  * @param callApi - API caller function (config-based for CLI, Bearer-based for HTTP)
@@ -89,7 +89,7 @@ function registerGroupTools(server, callApi) {
89
89
  "lexq_groups_update",
90
90
  {
91
91
  title: "Update Policy Group",
92
- description: "Update a policy group. This is a full replacement \u2014 omitted optional fields will be set to null on the server.",
92
+ description: "Update a policy group. Only provided fields are updated; omitted fields remain unchanged.",
93
93
  inputSchema: {
94
94
  groupId: z.string().uuid().describe("Policy group ID"),
95
95
  name: z.string().optional().describe("New name"),
@@ -186,10 +186,10 @@ function registerVersionTools(server, callApi) {
186
186
  "lexq_versions_create",
187
187
  {
188
188
  title: "Create Policy Version",
189
- description: "Create a new DRAFT version in a policy group. Provide a commit message and optional effective date range.",
189
+ description: "Create a new DRAFT version in a policy group. Optionally provide a commit message and effective date range.",
190
190
  inputSchema: {
191
191
  groupId: z2.string().uuid().describe("Policy group ID"),
192
- commitMessage: z2.string().describe("Commit message describing this version"),
192
+ commitMessage: z2.string().optional().describe("Commit message describing this version"),
193
193
  effectiveFrom: z2.string().optional().describe("Effective start date (ISO 8601)"),
194
194
  effectiveTo: z2.string().optional().describe("Effective end date (ISO 8601)")
195
195
  }
@@ -200,7 +200,7 @@ function registerVersionTools(server, callApi) {
200
200
  "lexq_versions_update",
201
201
  {
202
202
  title: "Update Policy Version",
203
- description: "Update a DRAFT version. Only DRAFT versions can be modified.",
203
+ description: "Update a DRAFT version. Only DRAFT versions can be modified. Only provided fields are changed.",
204
204
  inputSchema: {
205
205
  groupId: z2.string().uuid().describe("Policy group ID"),
206
206
  versionId: z2.string().uuid().describe("Version ID"),
@@ -243,6 +243,7 @@ function registerVersionTools(server, callApi) {
243
243
 
244
244
  // src/mcp/tools/rules.ts
245
245
  import { z as z3 } from "zod";
246
+ import dedent from "dedent";
246
247
  function registerRuleTools(server, callApi) {
247
248
  server.registerTool(
248
249
  "lexq_rules_list",
@@ -277,32 +278,34 @@ function registerRuleTools(server, callApi) {
277
278
  "lexq_rules_create",
278
279
  {
279
280
  title: "Create Rule",
280
- description: `Create a rule in a DRAFT version. Requires name, priority, condition tree, and actions array.
281
+ description: dedent`
282
+ Create a rule in a DRAFT version. Requires name, priority, condition tree, and actions array.
281
283
 
282
- Before creating rules with new fact keys, call lexq_facts_list to check existing facts.
283
- If a required key is missing, ask the user to confirm the type, isRequired, and description
284
- before calling lexq_facts_create \u2014 registering facts enables type validation, Console UI
285
- autocomplete, and the dry-run requirements analyzer.
284
+ Before creating rules with new fact keys, call lexq_facts_list to check existing facts.
285
+ If a required key is missing, ask the user to confirm the type, isRequired, and description
286
+ before calling lexq_facts_create registering facts enables type validation, Console UI
287
+ autocomplete, and the dry-run requirements analyzer.
286
288
 
287
- Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator: "AND"|"OR", children: [...] }
288
- Operators: EQUALS, NOT_EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, CONTAINS, IN, NOT_IN
289
- Value types: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER
289
+ Condition: { type: "SINGLE", field, operator, value, valueType } or { type: "GROUP", operator: "AND"|"OR", children: [...] }
290
+ Operators: EQUALS, NOT_EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, CONTAINS, IN, NOT_IN
291
+ Value types: STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER
290
292
 
291
- Actions: [{ type, parameters }]
293
+ Actions: [{ type, parameters }]
292
294
 
293
- Action parameter schemas:
294
- - DISCOUNT: { refVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT) }
295
- - POINT: { refVar: string, targetVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT), integrationId: uuid }
296
- - COUPON_ISSUE: { couponId: string, integrationId: uuid }
297
- - BLOCK: { reason: string }
298
- - NOTIFICATION: { channel: "SMS"|"EMAIL"|"PUSH", targetVar: string, templateId: string, integrationId: uuid }
299
- - WEBHOOK: { url: string, method: "POST", payloadTemplate?: object } payloadTemplate is optional. Without it, all facts are sent as-is. With it, the object is sent as the HTTP body with {{variables}} replaced at execution time. Variables: {{fact.xxx}}, {{output.xxx}}, {{timestamp}}, {{ruleName}}, {{groupName}}, {{versionNo}}, {{xxx}} (shorthand).
300
- Platform examples:
301
- Slack: { "text": "Rule {{ruleName}} fired \u2014 {{fact.customer_tier}}" }
302
- Discord: { "content": "Rule {{ruleName}} fired \u2014 {{fact.customer_tier}}" }
303
- Generic: { "event": "rule_matched", "rule": "{{ruleName}}", "amount": "{{output.payment_amount}}" }
304
- - SET_FACT: { key: string, value: string|number|boolean }
305
- - ADD_TAG: { tag: string, targetVar: string }`,
295
+ Action parameter schemas:
296
+ - DISCOUNT: { refVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT) }
297
+ - POINT: { refVar: string, targetVar: string, method: "PERCENTAGE"|"AMOUNT", rate?: number (when PERCENTAGE), value?: number (when AMOUNT), integrationId: uuid }
298
+ - COUPON_ISSUE: { couponId: string, integrationId: uuid }
299
+ - BLOCK: { reason: string }
300
+ - NOTIFICATION: { channel: "SMS"|"EMAIL"|"PUSH", targetVar: string, templateId: string, integrationId: uuid }
301
+ - WEBHOOK: { url: string, method: "POST", payloadTemplate?: object } payloadTemplate is optional. Without it, all facts are sent as-is. With it, the object is sent as the HTTP body with {{variables}} replaced at execution time. Variables: {{fact.xxx}}, {{output.xxx}}, {{timestamp}}, {{ruleName}}, {{groupName}}, {{versionNo}}, {{xxx}} (shorthand).
302
+ Platform examples:
303
+ Slack: { "text": "Rule {{ruleName}} fired {{fact.customer_tier}}" }
304
+ Discord: { "content": "Rule {{ruleName}} fired {{fact.customer_tier}}" }
305
+ Generic: { "event": "rule_matched", "rule": "{{ruleName}}", "amount": "{{output.payment_amount}}" }
306
+ - SET_FACT: { key: string, value: string|number|boolean }
307
+ - ADD_TAG: { tag: string, targetVar: string }
308
+ `,
306
309
  inputSchema: {
307
310
  groupId: z3.string().uuid().describe("Policy group ID"),
308
311
  versionId: z3.string().uuid().describe("Version ID"),
@@ -435,12 +438,12 @@ function registerFactTools(server, callApi) {
435
438
  "lexq_facts_update",
436
439
  {
437
440
  title: "Update Fact Definition",
438
- description: "Update a fact definition. Key and type cannot be changed. System facts only allow name and description changes.",
441
+ description: "Update a fact definition. Key and type cannot be changed. Only provided fields are updated. System facts only allow name and description changes.",
439
442
  inputSchema: {
440
443
  factId: z4.string().uuid().describe("Fact definition ID"),
441
- name: z4.string().describe("Display name"),
444
+ name: z4.string().optional().describe("Display name"),
442
445
  description: z4.string().optional().describe("Description"),
443
- isRequired: z4.boolean().describe("Required flag")
446
+ isRequired: z4.boolean().optional().describe("Required flag")
444
447
  }
445
448
  },
446
449
  async ({ factId, ...body }) => callApi("PUT", `schema/facts/${factId}`, { body })
@@ -456,6 +459,15 @@ function registerFactTools(server, callApi) {
456
459
  },
457
460
  async ({ factId }) => callApi("DELETE", `schema/facts/${factId}`)
458
461
  );
462
+ server.registerTool(
463
+ "lexq_facts_action_metadata",
464
+ {
465
+ title: "Get Action Runtime Fact Metadata",
466
+ description: "Retrieve runtime Facts metadata for each Action type. Shows which Facts are required as input, produced as output, or consumed at runtime by each Action (DISCOUNT, SEND_SMS, ADD_TAG, SET_FACT, etc.). Use this BEFORE designing rules to understand which Action produces which output variables (e.g., DISCOUNT produces last_discount_amount). Data is static and changes only on engine deployment \u2014 safe to cache in-session.",
467
+ inputSchema: {}
468
+ },
469
+ async () => callApi("GET", "schema/action-metadata")
470
+ );
459
471
  }
460
472
 
461
473
  // src/mcp/tools/deploy.ts
@@ -469,7 +481,7 @@ function registerDeployTools(server, callApi) {
469
481
  inputSchema: {
470
482
  groupId: z5.string().uuid().describe("Policy group ID"),
471
483
  versionId: z5.string().uuid().describe("Version ID to publish"),
472
- memo: z5.string().min(1).describe("Publish Deployment memo (required)")
484
+ memo: z5.string().min(1).describe("Publish memo (required)")
473
485
  }
474
486
  },
475
487
  async ({ groupId, versionId, memo }) => callApi("POST", `policy-groups/${groupId}/versions/${versionId}/publish`, { body: { memo } })
@@ -482,7 +494,7 @@ function registerDeployTools(server, callApi) {
482
494
  inputSchema: {
483
495
  groupId: z5.string().uuid().describe("Policy group ID"),
484
496
  versionId: z5.string().uuid().describe("Version ID to deploy"),
485
- memo: z5.string().min(1).describe("Live Deployment memo (required)")
497
+ memo: z5.string().min(1).describe("Deployment memo (required)")
486
498
  }
487
499
  },
488
500
  async ({ groupId, versionId, memo }) => callApi("POST", `policy-groups/${groupId}/deploy`, {
@@ -496,7 +508,7 @@ function registerDeployTools(server, callApi) {
496
508
  description: "Rollback to the previous deployed version. Only available if there is a previous version.",
497
509
  inputSchema: {
498
510
  groupId: z5.string().uuid().describe("Policy group ID"),
499
- memo: z5.string().default("").describe("Rollback reason")
511
+ memo: z5.string().min(1).describe("Rollback reason (required)")
500
512
  }
501
513
  },
502
514
  async ({ groupId, memo }) => callApi("POST", `policy-groups/${groupId}/rollback`, {
@@ -510,7 +522,7 @@ function registerDeployTools(server, callApi) {
510
522
  description: "Remove the live version from traffic. The version stays ACTIVE but no longer serves requests.",
511
523
  inputSchema: {
512
524
  groupId: z5.string().uuid().describe("Policy group ID"),
513
- memo: z5.string().default("").describe("Undeploy reason")
525
+ memo: z5.string().min(1).describe("Undeploy reason (required)")
514
526
  }
515
527
  },
516
528
  async ({ groupId, memo }) => callApi("POST", `policy-groups/${groupId}/undeploy`, {
@@ -526,13 +538,19 @@ function registerDeployTools(server, callApi) {
526
538
  page: z5.number().int().min(0).default(0).describe("Page number"),
527
539
  size: z5.number().int().min(1).max(100).default(20).describe("Page size"),
528
540
  groupId: z5.string().uuid().optional().describe("Filter by group ID"),
529
- type: z5.enum(["PUBLISH", "DEPLOY", "ROLLBACK", "UNDEPLOY"]).optional().describe("Filter by deployment type")
541
+ types: z5.string().optional().describe(
542
+ "Filter by deployment types (comma-separated: PUBLISH,DEPLOY,ROLLBACK,UNDEPLOY)"
543
+ ),
544
+ startDate: z5.string().optional().describe("Start date (yyyy-MM-dd)"),
545
+ endDate: z5.string().optional().describe("End date (yyyy-MM-dd)")
530
546
  }
531
547
  },
532
- async ({ page, size, groupId, type }) => {
548
+ async ({ page, size, groupId, types, startDate, endDate }) => {
533
549
  const params = paginationParams(page, size);
534
550
  if (groupId) params.groupId = groupId;
535
- if (type) params.type = type;
551
+ if (types) params.types = types;
552
+ if (startDate) params.startDate = startDate;
553
+ if (endDate) params.endDate = endDate;
536
554
  return callApi("GET", "deployments", { params });
537
555
  }
538
556
  );
@@ -556,18 +574,53 @@ function registerDeployTools(server, callApi) {
556
574
  },
557
575
  async () => callApi("GET", "deployments/overview")
558
576
  );
577
+ server.registerTool(
578
+ "lexq_deploy_deployable",
579
+ {
580
+ title: "List Deployable Versions",
581
+ description: "List ACTIVE (published) versions that can be deployed for a group. Use this to find which versions are available before calling deploy live.",
582
+ inputSchema: {
583
+ groupId: z5.string().uuid().describe("Policy group ID")
584
+ }
585
+ },
586
+ async ({ groupId }) => callApi("GET", `deployments/groups/${groupId}/deployable-versions`)
587
+ );
588
+ server.registerTool(
589
+ "lexq_deploy_diff",
590
+ {
591
+ title: "Deployment Diff",
592
+ description: "Compare rule snapshots between two versions. Shows added, removed, and modified rules. Useful for reviewing changes before deploying a new version.",
593
+ inputSchema: {
594
+ baseVersionId: z5.string().uuid().describe("Base version ID (typically the current live)"),
595
+ targetVersionId: z5.string().uuid().describe("Target version ID (the one you want to deploy)")
596
+ }
597
+ },
598
+ async ({ baseVersionId, targetVersionId }) => callApi("GET", "deployments/diff", {
599
+ params: { baseVersionId, targetVersionId }
600
+ })
601
+ );
559
602
  }
560
603
 
561
604
  // src/mcp/tools/analytics.ts
562
605
  import { z as z6 } from "zod";
606
+ import dedent2 from "dedent";
563
607
  function registerAnalyticsTools(server, callApi) {
564
608
  server.registerTool(
565
609
  "lexq_dry_run",
566
610
  {
567
611
  title: "Dry Run",
568
- description: `Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
569
- Example input: { "facts": { "payment_amount": 100000, "customer_tier": "VIP" } }
570
- Always dry-run before publishing to validate rule behavior.`,
612
+ description: dedent2`
613
+ Execute a single dry run against a version. Tests how rules evaluate given input facts without side effects.
614
+
615
+ Returns:
616
+ inputFacts — normalized input facts
617
+ mutatedFacts — input facts changed by rule actions (e.g. DISCOUNT mutates payment_amount)
618
+ generatedVariables — new variables created by rules (e.g. last_discount_amount)
619
+ executionTraces — per-rule match status
620
+ decisionTraces — per-rule decision (SELECTED / BLOCKED_MUTEX / etc.)
621
+
622
+ Example input: { "facts": { "payment_amount": 100000, "customer_tier": "VIP" } }
623
+ Always dry-run before publishing to validate rule behavior.`,
571
624
  inputSchema: {
572
625
  versionId: z6.string().uuid().describe("Policy version ID to test against"),
573
626
  facts: z6.string().describe('JSON string of facts object, e.g. {"payment_amount":100000}'),
@@ -586,7 +639,13 @@ function registerAnalyticsTools(server, callApi) {
586
639
  "lexq_dry_run_compare",
587
640
  {
588
641
  title: "Dry Run Compare",
589
- description: "Compare dry run results between two versions using the same input facts. Useful for validating changes.",
642
+ description: dedent2`
643
+ Compare dry run results between two versions using the same input facts. Useful for validating changes.
644
+
645
+ Returns:
646
+ resultA / resultB — full DryRunResponse for each version
647
+ diff.mutatedDiff — changes in mutatedFacts between A and B (key → {before, after})
648
+ diff.generatedDiff — changes in generatedVariables between A and B`,
590
649
  inputSchema: {
591
650
  versionIdA: z6.string().uuid().describe("Baseline version ID"),
592
651
  versionIdB: z6.string().uuid().describe("Candidate version ID"),
@@ -616,21 +675,23 @@ function registerAnalyticsTools(server, callApi) {
616
675
  "lexq_simulation_start",
617
676
  {
618
677
  title: "Start Simulation",
619
- description: `Start a batch simulation against historical or uploaded data.
620
-
621
- dataset.type: "HISTORICAL" or "UPLOADED"
622
- dataset.source (when HISTORICAL): "EXECUTION_LOGS"
623
- dataset.from / dataset.to: date range (yyyy-MM-dd, when HISTORICAL)
624
- options.maxRecords: number (max 100000, default 10000)
625
- options.baselinePolicyVersionId: uuid (optional, for comparison)
626
- options.includeRuleStats: boolean
627
-
628
- Example body:
629
- {
630
- "policyVersionId": "<uuid>",
631
- "dataset": { "type": "HISTORICAL", "source": "EXECUTION_LOGS", "from": "2025-01-01", "to": "2025-01-31" },
632
- "options": { "baselinePolicyVersionId": "<uuid>", "includeRuleStats": true, "maxRecords": 10000 }
633
- }`,
678
+ description: dedent2`
679
+ Start a batch simulation against historical or uploaded data.
680
+
681
+ dataset.type: "HISTORICAL" or "UPLOADED"
682
+ dataset.source (when HISTORICAL): "EXECUTION_LOGS"
683
+ dataset.from / dataset.to: date range (yyyy-MM-dd, when HISTORICAL)
684
+ options.maxRecords: number (max 100000, default 10000)
685
+ options.baselinePolicyVersionId: uuid (optional, for comparison)
686
+ options.includeRuleStats: boolean
687
+
688
+ Example body:
689
+ {
690
+ "policyVersionId": "<uuid>",
691
+ "dataset": { "type": "HISTORICAL", "source": "EXECUTION_LOGS", "from": "2025-01-01", "to": "2025-01-31" },
692
+ "options": { "baselinePolicyVersionId": "<uuid>", "includeRuleStats": true, "maxRecords": 10000 }
693
+ }
694
+ `,
634
695
  inputSchema: {
635
696
  body: z6.string().describe("JSON string of SimulationRequest")
636
697
  }
@@ -701,16 +762,18 @@ function registerAnalyticsTools(server, callApi) {
701
762
  "lexq_dataset_upload",
702
763
  {
703
764
  title: "Upload Dataset",
704
- description: `Upload inline CSV or JSON content as a simulation dataset.
705
- The content is uploaded to S3 and a path is returned.
706
- Use this path in simulation start with dataset type UPLOADED.
707
-
708
- CSV example:
709
- user_id,payment_amount
710
- user_001,150000
711
- user_002,50000
712
-
713
- JSON example: [{"user_id":"user_001","payment_amount":150000}, {"user_id":"user_002","payment_amount":50000}]`,
765
+ description: dedent2`
766
+ Upload inline CSV or JSON content as a simulation dataset.
767
+ The content is uploaded to S3 and a path is returned.
768
+ Use this path in simulation start with dataset type UPLOADED.
769
+
770
+ CSV example:
771
+ user_id,payment_amount
772
+ user_001,150000
773
+ user_002,50000
774
+
775
+ JSON example: [{"user_id":"user_001","payment_amount":150000}, {"user_id":"user_002","payment_amount":50000}]
776
+ `,
714
777
  inputSchema: {
715
778
  content: z6.string().describe("CSV or JSON content as string"),
716
779
  filename: z6.string().default("dataset.csv").describe("Filename with extension (.csv or .json)")
@@ -893,8 +956,16 @@ var TaskType = [
893
956
  "WEBHOOK_EXECUTE",
894
957
  // Internal
895
958
  "IMAGE_PROCESSING",
896
- "DAILY_SETTLEMENT"
959
+ "DAILY_SETTLEMENT",
960
+ "PLATFORM_WEBHOOK"
897
961
  ];
962
+ var PlatformEventType = [
963
+ "VERSION_PUBLISHED",
964
+ "DEPLOYED",
965
+ "ROLLED_BACK",
966
+ "UNDEPLOYED"
967
+ ];
968
+ var WebhookPayloadFormat = ["GENERIC", "SLACK"];
898
969
 
899
970
  // src/mcp/tools/logs.ts
900
971
  function registerLogTools(server, callApi) {
@@ -940,14 +1011,14 @@ function registerLogTools(server, callApi) {
940
1011
  "lexq_logs_action",
941
1012
  {
942
1013
  title: "Process Failure Log",
943
- description: "Process a single failure log: RETRY, RESOLVE, or IGNORE.",
1014
+ description: "Process a single failure log: RETRY (re-execute with original payload), RESOLVE (mark as manually fixed), or IGNORE (skip intentionally).",
944
1015
  inputSchema: {
945
1016
  logId: z9.string().uuid().describe("Failure log ID"),
946
1017
  action: z9.enum(FailureAction).describe("Action to take")
947
1018
  }
948
1019
  },
949
1020
  async ({ logId, action }) => callApi("POST", `failure-logs/${logId}/actions`, {
950
- body: { action }
1021
+ params: { action }
951
1022
  })
952
1023
  );
953
1024
  server.registerTool(
@@ -966,6 +1037,76 @@ function registerLogTools(server, callApi) {
966
1037
  );
967
1038
  }
968
1039
 
1040
+ // src/mcp/tools/webhook-subscriptions.ts
1041
+ import { z as z10 } from "zod";
1042
+ function registerWebhookSubscriptionTools(server, callApi) {
1043
+ server.registerTool(
1044
+ "lexq_webhook_subscriptions_list",
1045
+ {
1046
+ title: "List Webhook Subscriptions",
1047
+ description: "List platform event webhook subscriptions. These receive deployment lifecycle notifications (publish, deploy, rollback, undeploy).",
1048
+ inputSchema: {
1049
+ page: z10.number().int().min(0).default(0).describe("Page number"),
1050
+ size: z10.number().int().min(1).max(100).default(20).describe("Page size")
1051
+ }
1052
+ },
1053
+ async ({ page, size }) => {
1054
+ const params = paginationParams(page, size);
1055
+ return callApi("GET", "webhook-subscriptions", { params });
1056
+ }
1057
+ );
1058
+ server.registerTool(
1059
+ "lexq_webhook_subscriptions_get",
1060
+ {
1061
+ title: "Get Webhook Subscription",
1062
+ description: "Get webhook subscription detail by ID.",
1063
+ inputSchema: {
1064
+ id: z10.string().uuid().describe("Webhook subscription ID")
1065
+ }
1066
+ },
1067
+ async ({ id }) => callApi("GET", `webhook-subscriptions/${id}`)
1068
+ );
1069
+ server.registerTool(
1070
+ "lexq_webhook_subscriptions_save",
1071
+ {
1072
+ title: "Save Webhook Subscription",
1073
+ description: 'Create or update a webhook subscription. Omit id to create, provide id to update. Events: VERSION_PUBLISHED, DEPLOYED, ROLLED_BACK, UNDEPLOYED. Formats: GENERIC (full JSON), SLACK ({"text": "..."}).',
1074
+ inputSchema: {
1075
+ id: z10.string().uuid().optional().describe("Subscription ID (omit to create, provide to update)"),
1076
+ name: z10.string().min(1).describe("Subscription name (unique per tenant)"),
1077
+ webhookUrl: z10.string().url().describe("Webhook endpoint URL"),
1078
+ subscribedEvents: z10.array(z10.enum(PlatformEventType)).min(1).describe("Events to subscribe to"),
1079
+ payloadFormat: z10.enum(WebhookPayloadFormat).optional().default("GENERIC").describe("Payload format"),
1080
+ secret: z10.string().optional().describe("HMAC-SHA256 signing secret"),
1081
+ isActive: z10.boolean().optional().default(true).describe("Whether the subscription is active")
1082
+ }
1083
+ },
1084
+ async ({ ...body }) => callApi("POST", "webhook-subscriptions", { body })
1085
+ );
1086
+ server.registerTool(
1087
+ "lexq_webhook_subscriptions_delete",
1088
+ {
1089
+ title: "Delete Webhook Subscription",
1090
+ description: "Delete a webhook subscription by ID.",
1091
+ inputSchema: {
1092
+ id: z10.string().uuid().describe("Webhook subscription ID")
1093
+ }
1094
+ },
1095
+ async ({ id }) => callApi("DELETE", `webhook-subscriptions/${id}`)
1096
+ );
1097
+ server.registerTool(
1098
+ "lexq_webhook_subscriptions_test",
1099
+ {
1100
+ title: "Test Webhook Subscription",
1101
+ description: "Send a test event to verify webhook connectivity. Returns the HTTP status code and success/failure message.",
1102
+ inputSchema: {
1103
+ id: z10.string().uuid().describe("Webhook subscription ID")
1104
+ }
1105
+ },
1106
+ async ({ id }) => callApi("POST", `webhook-subscriptions/${id}/test`)
1107
+ );
1108
+ }
1109
+
969
1110
  // src/mcp/register.ts
970
1111
  function registerAllTools(server, callApi) {
971
1112
  registerStatusTools(server, callApi);
@@ -978,6 +1119,7 @@ function registerAllTools(server, callApi) {
978
1119
  registerHistoryTools(server, callApi);
979
1120
  registerIntegrationTools(server, callApi);
980
1121
  registerLogTools(server, callApi);
1122
+ registerWebhookSubscriptionTools(server, callApi);
981
1123
  }
982
1124
  export {
983
1125
  paginationParams,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexq/cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "LexQ CLI — manage policies, simulate rules, and deploy from the terminal. Built for humans and AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,12 +47,14 @@
47
47
  "@modelcontextprotocol/sdk": "^1.28.0",
48
48
  "cli-table3": "^0.6.5",
49
49
  "commander": "^13.1.0",
50
+ "dedent": "^1.7.2",
50
51
  "ora": "^8.2.0",
51
52
  "prettier": "^3.8.1",
52
53
  "zod": "^3.25.76"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@eslint/js": "^9.20.0",
57
+ "@types/dedent": "^0.7.2",
56
58
  "@types/node": "^22.12.0",
57
59
  "eslint": "^10.2.0",
58
60
  "tsup": "^8.5.1",
@@ -4,7 +4,9 @@
4
4
 
5
5
  ## What is LexQ?
6
6
 
7
- LexQ is a **policy execution engine**. Customers define business rules (conditions → actions), deploy them to production, and execute them via API — all without touching application code. Core differentiators are **pre-deploy simulation** and **A/B testing** for rule versions.
7
+ LexQ is a **policy execution engine**. Customers define business rules (conditions → actions), deploy them to
8
+ production, and execute them via API — all without touching application code. Core differentiators are **pre-deploy
9
+ simulation** and **A/B testing** for rule versions.
8
10
 
9
11
  ## Authentication
10
12
 
@@ -20,22 +22,22 @@ lexq auth whoami
20
22
  The API key is passed as `X-API-KEY` header on every request. You can also override per-command:
21
23
 
22
24
  ```bash
23
- lexq groups list --api-key lxk_override_key
25
+ lexq groups list --api-key sk_live_override_key
24
26
  ```
25
27
 
26
28
  **Base URL:** `https://api.lexq.io/api/v1/partners` (default). Override with `--base-url`.
27
29
 
28
30
  ## Core Concepts
29
31
 
30
- | Concept | Description | Analogy |
31
- |---|---|---|
32
- | **Policy Group** | A container for rule versions. Has a lifecycle status (ACTIVE / DISABLED / ARCHIVED). | Git repository |
33
- | **Policy Version** | A snapshot of rules within a group. Follows DRAFT → ACTIVE → ARCHIVED lifecycle. | Git branch / commit |
34
- | **Policy Rule** | A condition + actions pair within a version. Evaluated in priority order. | if-then statement |
35
- | **Fact Definition** | Input schema — declares available variables and their types (STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER). | Function parameter |
36
- | **Deployment** | Promotes a PUBLISHED version to live traffic. Supports rollback. | Production release |
37
- | **Dry Run** | Tests a single input against a DRAFT or ACTIVE version without side effects. | Unit test |
38
- | **Simulation** | Batch-tests a version against historical execution data. Compares with a baseline. | Integration test suite |
32
+ | Concept | Description | Analogy |
33
+ |---------------------|------------------------------------------------------------------------------------------------------------------|------------------------|
34
+ | **Policy Group** | A container for rule versions. Has a lifecycle status (ACTIVE / DISABLED / ARCHIVED). | Git repository |
35
+ | **Policy Version** | A snapshot of rules within a group. Follows DRAFT → ACTIVE → ARCHIVED lifecycle. | Git branch / commit |
36
+ | **Policy Rule** | A condition + actions pair within a version. Evaluated in priority order. | if-then statement |
37
+ | **Fact Definition** | Input schema — declares available variables and their types (STRING, NUMBER, BOOLEAN, LIST_STRING, LIST_NUMBER). | Function parameter |
38
+ | **Deployment** | Promotes a PUBLISHED version to live traffic. Supports rollback. | Production release |
39
+ | **Dry Run** | Tests a single input against a DRAFT or ACTIVE version without side effects. | Unit test |
40
+ | **Simulation** | Batch-tests a version against historical execution data. Compares with a baseline. | Integration test suite |
39
41
 
40
42
  ## Standard Workflow
41
43
 
@@ -64,32 +66,33 @@ This is the typical lifecycle. **Always follow this order:**
64
66
 
65
67
  Every command accepts these flags:
66
68
 
67
- | Flag | Description | Default |
68
- |---|---|---|
69
- | `--format <json\|table>` | Output format | `json` |
70
- | `--api-key <key>` | Override stored API key | from config |
71
- | `--base-url <url>` | Override API base URL | `https://api.lexq.io/api/v1/partners` |
72
- | `--dry-run` | Preview the HTTP request without executing | off |
73
- | `--verbose` | Show request/response details | off |
74
- | `--no-color` | Disable colored output | off |
69
+ | Flag | Description | Default |
70
+ |--------------------------|--------------------------------------------|---------------------------------------|
71
+ | `--format <json\|table>` | Output format | `json` |
72
+ | `--api-key <key>` | Override stored API key | from config |
73
+ | `--base-url <url>` | Override API base URL | `https://api.lexq.io/api/v1/partners` |
74
+ | `--dry-run` | Preview the HTTP request without executing | off |
75
+ | `--verbose` | Show request/response details | off |
76
+ | `--no-color` | Disable colored output | off |
75
77
 
76
- **Agent best practice:** Always use `--format json` (the default). Parse JSON output programmatically. Use `--format table` only when displaying to humans.
78
+ **Agent best practice:** Always use `--format json` (the default). Parse JSON output programmatically. Use
79
+ `--format table` only when displaying to humans.
77
80
 
78
81
  ## Command Groups
79
82
 
80
- | Group | Commands | Description |
81
- |---|---|---|
82
- | `auth` | `login`, `logout`, `whoami` | Authentication |
83
- | `status` | (root) | API health check |
84
- | `groups` | `list`, `get`, `create`, `update`, `delete` + `ab-test start\|stop\|adjust` | Policy group CRUD + A/B testing |
85
- | `versions` | `list`, `get`, `create`, `update`, `delete`, `clone` | Version CRUD |
86
- | `rules` | `list`, `get`, `create`, `update`, `delete`, `reorder`, `toggle` | Rule CRUD |
87
- | `facts` | `list`, `create`, `update`, `delete` | Fact definition CRUD |
88
- | `deploy` | `publish`, `live`, `rollback`, `undeploy`, `history`, `detail`, `overview` | Deployment lifecycle |
89
- | `analytics` | `dry-run`, `dry-run-compare`, `requirements`, `simulation start/status/list/cancel/export` | Testing & analysis |
90
- | `history` | `list`, `get`, `stats` | Execution history |
91
- | `integrations` | `list`, `get`, `save`, `delete`, `config-spec` | External integrations |
92
- | `logs` | `list`, `get`, `action`, `bulk-action` | Failure log management |
83
+ | Group | Commands | Description |
84
+ |----------------|--------------------------------------------------------------------------------------------|---------------------------------|
85
+ | `auth` | `login`, `logout`, `whoami` | Authentication |
86
+ | `status` | (root) | API health check |
87
+ | `groups` | `list`, `get`, `create`, `update`, `delete` + `ab-test start\|stop\|adjust` | Policy group CRUD + A/B testing |
88
+ | `versions` | `list`, `get`, `create`, `update`, `delete`, `clone` | Version CRUD |
89
+ | `rules` | `list`, `get`, `create`, `update`, `delete`, `reorder`, `toggle` | Rule CRUD |
90
+ | `facts` | `list`, `create`, `update`, `delete` | Fact definition CRUD |
91
+ | `deploy` | `publish`, `live`, `rollback`, `undeploy`, `history`, `detail`, `overview` | Deployment lifecycle |
92
+ | `analytics` | `dry-run`, `dry-run-compare`, `requirements`, `simulation start/status/list/cancel/export` | Testing & analysis |
93
+ | `history` | `list`, `get`, `stats` | Execution history |
94
+ | `integrations` | `list`, `get`, `save`, `delete`, `config-spec` | External integrations |
95
+ | `logs` | `list`, `get`, `action`, `bulk-action` | Failure log management |
93
96
 
94
97
  ## Pagination
95
98
 
@@ -97,7 +100,9 @@ All list endpoints return a `PageResponse`:
97
100
 
98
101
  ```json
99
102
  {
100
- "content": [...],
103
+ "content": [
104
+ ...
105
+ ],
101
106
  "totalElements": 42,
102
107
  "totalPages": 3,
103
108
  "pageNo": 0,
@@ -121,13 +126,13 @@ API errors return:
121
126
 
122
127
  **Common error codes and what to do:**
123
128
 
124
- | Code | Meaning | Action |
125
- |---|---|---|
126
- | `ENTITY_NOT_FOUND` | Resource doesn't exist | Verify the ID. Run the corresponding `list` command. |
127
- | `INVALID_INPUT` | Validation failed | Check required fields. Run `lexq analytics requirements` for fact requirements. |
128
- | `CANNOT_MODIFY` | Version is not DRAFT | Clone the version to create a new DRAFT: `lexq versions clone` |
129
- | `EMPTY_RULES` | Publish attempted with 0 rules | Add at least one rule before publishing. |
130
- | `UNAUTHORIZED` | Invalid or missing API key | Run `lexq auth login` with a valid key. |
129
+ | Code | Meaning | Action |
130
+ |--------------------|--------------------------------|---------------------------------------------------------------------------------|
131
+ | `ENTITY_NOT_FOUND` | Resource doesn't exist | Verify the ID. Run the corresponding `list` command. |
132
+ | `INVALID_INPUT` | Validation failed | Check required fields. Run `lexq analytics requirements` for fact requirements. |
133
+ | `CANNOT_MODIFY` | Version is not DRAFT | Clone the version to create a new DRAFT: `lexq versions clone` |
134
+ | `EMPTY_RULES` | Publish attempted with 0 rules | Add at least one rule before publishing. |
135
+ | `UNAUTHORIZED` | Invalid or missing API key | Run `lexq auth login` with a valid key. |
131
136
 
132
137
  ## Important Conventions
133
138
 
@@ -136,4 +141,5 @@ API errors return:
136
141
  3. **Dates use ISO 8601.** Example: `2025-01-01T00:00:00Z`. Time zone is UTC.
137
142
  4. **JSON bodies via `--json`.** Most create/update commands accept `--json '<body>'` for the request body.
138
143
  5. **File input via `--file`.** Analytics commands accept `--file path/to/body.json` as an alternative to `--json`.
139
- 6. **Confirmation prompts.** Destructive operations (delete, cancel, undeploy) prompt for confirmation. Use `--force` to skip in automation.
144
+ 6. **Confirmation prompts.** Destructive operations (delete, cancel, undeploy) prompt for confirmation. Use `--force` to
145
+ skip in automation.