@lexq/cli 1.0.2 → 1.1.0
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 +6 -2
- package/dist/index.js +31 -18
- package/dist/mcp/register.js +9 -7
- package/package.json +2 -1
- package/skills/lexq-execution/SKILL.md +1 -1
package/AGENTS.md
CHANGED
|
@@ -180,8 +180,12 @@ The MCP toolset mirrors the CLI command inventory one-to-one.
|
|
|
180
180
|
- Releasing is two steps, in this order. Raise `version` in package.json inside the pull request, so a reviewer
|
|
181
181
|
sees the number beside the change that justifies it. After it merges, push the matching `v<version>` tag — that
|
|
182
182
|
tag is what publishes. A tag that disagrees with package.json is rejected before anything reaches npm.
|
|
183
|
-
- Pick the number by what breaks.
|
|
184
|
-
|
|
183
|
+
- Pick the number by what breaks. A change is breaking when a call that used to work now fails or does something
|
|
184
|
+
else. That includes removing or renaming a tool, command, option or parameter, changing a parameter's type,
|
|
185
|
+
adding a required parameter or making an optional one required, and narrowing the values a parameter accepts.
|
|
186
|
+
Raising the Node floor or moving a peer dependency to a new major version is breaking too. A breaking change
|
|
187
|
+
raises the major number. A new tool, command, option or optional parameter raises the minor, and anything else,
|
|
188
|
+
including a changed description, raises the patch.
|
|
185
189
|
- Gates before commit: `pnpm typecheck` (zero errors), `pnpm lint` (zero warnings), `pnpm test:fact-key`,
|
|
186
190
|
`pnpm test:decimals`.
|
|
187
191
|
- Multi-line CLI help and MCP tool descriptions use `dedent` — template-literal indentation leaks into LLM context.
|
package/dist/index.js
CHANGED
|
@@ -804,18 +804,18 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
804
804
|
process.exit(1);
|
|
805
805
|
}
|
|
806
806
|
});
|
|
807
|
-
versions.command("update").description("Update a draft version metadata").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--commit-message <message>", "Commit message").option("--effective-from <instant>", `Effective start, ${INSTANT}`).option("--effective-to <instant>", `Effective end, ${INSTANT}`).option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
807
|
+
versions.command("update").description("Update a draft version metadata").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--id <versionId>", "Policy version ID").option("--commit-message <message>", "Commit message").option("--effective-from <instant>", `Effective start, ${INSTANT}`).option("--effective-to <instant>", `Effective end, ${INSTANT}`).option("--clear-effective-from", "Remove the effective start").option("--clear-effective-to", "Remove the effective end").option("--json <body>", "Full request body as JSON (overrides other options)").addHelpText(
|
|
808
808
|
"after",
|
|
809
809
|
dedent4`
|
|
810
810
|
|
|
811
811
|
Only DRAFT versions can be updated. Published (ACTIVE) versions are immutable.
|
|
812
812
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
both current values (read them with lexq versions get).
|
|
813
|
+
Omitted options keep their current values. To remove an end of the effective
|
|
814
|
+
window, pass --clear-effective-from or --clear-effective-to instead of a value.
|
|
816
815
|
|
|
817
|
-
|
|
816
|
+
Examples:
|
|
818
817
|
$ lexq versions update --group-id <gid> --id <vid> --commit-message "Updated rules"
|
|
818
|
+
$ lexq versions update --group-id <gid> --id <vid> --clear-effective-to
|
|
819
819
|
`
|
|
820
820
|
).action(async (opts) => {
|
|
821
821
|
try {
|
|
@@ -911,6 +911,8 @@ function buildUpdateBody(opts) {
|
|
|
911
911
|
if (opts.commitMessage) body.commitMessage = opts.commitMessage;
|
|
912
912
|
if (opts.effectiveFrom) body.effectiveFrom = opts.effectiveFrom;
|
|
913
913
|
if (opts.effectiveTo) body.effectiveTo = opts.effectiveTo;
|
|
914
|
+
if (opts.clearEffectiveFrom) body.clearEffectiveFrom = true;
|
|
915
|
+
if (opts.clearEffectiveTo) body.clearEffectiveTo = true;
|
|
914
916
|
return body;
|
|
915
917
|
}
|
|
916
918
|
|
|
@@ -1642,6 +1644,7 @@ function registerDeployCommands(program) {
|
|
|
1642
1644
|
|
|
1643
1645
|
Locks the version permanently. Rules cannot be modified after publishing.
|
|
1644
1646
|
A snapshot hash is generated for integrity verification.
|
|
1647
|
+
A version whose effective end has already passed cannot be published (P-058).
|
|
1645
1648
|
|
|
1646
1649
|
Example:
|
|
1647
1650
|
$ lexq deploy publish --group-id <gid> --version-id <vid> --memo "Validated via dry-run"
|
|
@@ -1672,6 +1675,8 @@ function registerDeployCommands(program) {
|
|
|
1672
1675
|
dedent7`
|
|
1673
1676
|
|
|
1674
1677
|
Takes effect immediately. The version starts receiving production traffic.
|
|
1678
|
+
A version whose effective start has not arrived (P-037) or whose effective end
|
|
1679
|
+
has passed (P-058) is rejected.
|
|
1675
1680
|
|
|
1676
1681
|
Example:
|
|
1677
1682
|
$ lexq deploy live --group-id <gid> --version-id <vid> --memo "Go live — v3"
|
|
@@ -1698,7 +1703,8 @@ function registerDeployCommands(program) {
|
|
|
1698
1703
|
dedent7`
|
|
1699
1704
|
|
|
1700
1705
|
Reverts to the version that was live before the current one.
|
|
1701
|
-
Only available if the previous version is still ACTIVE
|
|
1706
|
+
Only available if the previous version is still ACTIVE and its effective end
|
|
1707
|
+
has not passed (P-058).
|
|
1702
1708
|
|
|
1703
1709
|
Example:
|
|
1704
1710
|
$ lexq deploy rollback --group-id <gid> --memo "High error rate" --force
|
|
@@ -2201,7 +2207,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2201
2207
|
Use --format table for summary view.
|
|
2202
2208
|
`
|
|
2203
2209
|
);
|
|
2204
|
-
sim.command("start").description("Start a new batch simulation").
|
|
2210
|
+
sim.command("start").description("Start a new batch simulation").option("--json <body>", "Simulation request body as JSON").option("--file <path>", "Read request body from a JSON file").addHelpText(
|
|
2205
2211
|
"after",
|
|
2206
2212
|
dedent8`
|
|
2207
2213
|
|
|
@@ -2220,7 +2226,9 @@ function registerAnalyticsCommands(program) {
|
|
|
2220
2226
|
"maxRecords": 1000
|
|
2221
2227
|
}
|
|
2222
2228
|
}'
|
|
2223
|
-
|
|
2229
|
+
|
|
2230
|
+
$ lexq analytics simulation start --file simulation.json
|
|
2231
|
+
|
|
2224
2232
|
Dataset types: HISTORICAL, UPLOADED, MANUAL
|
|
2225
2233
|
Dataset sources: EXECUTION_LOGS, S3_BUCKET, REQUEST_BODY
|
|
2226
2234
|
HISTORICAL from / to are yyyy-MM-dd, whole days in the organization time zone, both inclusive.
|
|
@@ -2228,6 +2236,9 @@ function registerAnalyticsCommands(program) {
|
|
|
2228
2236
|
).action(async (opts) => {
|
|
2229
2237
|
try {
|
|
2230
2238
|
const globalOpts = program.opts();
|
|
2239
|
+
if (!opts.json && !opts.file) {
|
|
2240
|
+
throw new Error("Request body is required. Use --json or --file.");
|
|
2241
|
+
}
|
|
2231
2242
|
const body = resolveBody(opts);
|
|
2232
2243
|
const data = await apiRequest("POST", "analytics/simulations", {
|
|
2233
2244
|
apiKey: globalOpts.apiKey,
|
|
@@ -2578,7 +2589,7 @@ function registerProfileCommands(program) {
|
|
|
2578
2589
|
program.command("profile <groupId>").description("Per-rule latency profile with relative slow-rule flags").option(
|
|
2579
2590
|
"--rule <ruleId>",
|
|
2580
2591
|
`Single-rule detail (distributions + ${LATENCY_WINDOW_MILLIS / 1e3}s window series)`
|
|
2581
|
-
).option("--version <versionId>", "Version to inspect (default: live version)").option(
|
|
2592
|
+
).option("--version-id <versionId>", "Version to inspect (default: live version)").option(
|
|
2582
2593
|
"--from <instant>",
|
|
2583
2594
|
`Window start, ISO-8601 instant (default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago)`
|
|
2584
2595
|
).option("--to <instant>", "Window end, ISO-8601 instant (default: now)").option("--cache <state>", "Cache dimension for the rule table: HIT | MISS (default: HIT)").addHelpText(
|
|
@@ -2594,7 +2605,7 @@ function registerProfileCommands(program) {
|
|
|
2594
2605
|
Examples:
|
|
2595
2606
|
$ lexq profile <groupId>
|
|
2596
2607
|
$ lexq profile <groupId> --cache MISS --from 2026-07-01T00:00:00Z
|
|
2597
|
-
$ lexq profile <groupId> --rule <ruleId> --version <versionId>
|
|
2608
|
+
$ lexq profile <groupId> --rule <ruleId> --version-id <versionId>
|
|
2598
2609
|
`
|
|
2599
2610
|
).action(async (groupId, opts) => {
|
|
2600
2611
|
try {
|
|
@@ -2604,7 +2615,7 @@ function registerProfileCommands(program) {
|
|
|
2604
2615
|
throw new Error(`--cache must be one of: ${ProfileCacheState.join(" | ")}`);
|
|
2605
2616
|
}
|
|
2606
2617
|
const params = {};
|
|
2607
|
-
if (opts.
|
|
2618
|
+
if (opts.versionId) params.versionId = opts.versionId;
|
|
2608
2619
|
if (opts.from) params.from = opts.from;
|
|
2609
2620
|
if (opts.to) params.to = opts.to;
|
|
2610
2621
|
if (opts.cache) params.cacheState = opts.cache;
|
|
@@ -3743,13 +3754,15 @@ function registerVersionTools(server, callApi) {
|
|
|
3743
3754
|
{
|
|
3744
3755
|
title: "Update Policy Version",
|
|
3745
3756
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3746
|
-
description: "Update a DRAFT version. Only DRAFT versions can be modified.
|
|
3757
|
+
description: "Update a DRAFT version. Only DRAFT versions can be modified. Omitted fields keep their current values. To remove an end of the effective window, set clearEffectiveFrom or clearEffectiveTo to true instead of sending a value.",
|
|
3747
3758
|
inputSchema: z3.object({
|
|
3748
3759
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
3749
3760
|
versionId: z3.string().uuid().describe("Version ID"),
|
|
3750
3761
|
commitMessage: z3.string().optional().describe("New commit message"),
|
|
3751
|
-
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it
|
|
3752
|
-
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it
|
|
3762
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it to keep the current start.`),
|
|
3763
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it to keep the current end.`),
|
|
3764
|
+
clearEffectiveFrom: z3.boolean().optional().describe("Set to true to remove the effective start. Do not send effectiveFrom with it."),
|
|
3765
|
+
clearEffectiveTo: z3.boolean().optional().describe("Set to true to remove the effective end. Do not send effectiveTo with it.")
|
|
3753
3766
|
})
|
|
3754
3767
|
},
|
|
3755
3768
|
async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
|
|
@@ -4092,7 +4105,7 @@ function registerFactTools(server, callApi) {
|
|
|
4092
4105
|
{
|
|
4093
4106
|
title: "List Unregistered Facts",
|
|
4094
4107
|
annotations: { readOnlyHint: true },
|
|
4095
|
-
description: "List facts referenced by a version's rules but not yet defined (read-only \u2014 does not block publish/deploy
|
|
4108
|
+
description: "List facts referenced by a version's rules but not yet defined (read-only \u2014 does not block publish/deploy). Version-wide: covers every rule in the version. Each entry carries the inferred type, suggested name, and where it is referenced (condition/action). Register them with lexq_facts_create to enable type validation and the dry-run requirements analyzer.",
|
|
4096
4109
|
inputSchema: z5.object({
|
|
4097
4110
|
groupId: z5.string().uuid().describe("Policy group ID"),
|
|
4098
4111
|
versionId: z5.string().uuid().describe("Version ID")
|
|
@@ -4125,7 +4138,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4125
4138
|
{
|
|
4126
4139
|
title: "Publish Version",
|
|
4127
4140
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4128
|
-
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts referenced by rules do not block publishing
|
|
4141
|
+
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts referenced by rules do not block publishing; call lexq_facts_unregistered first to review them. Publishing fails if the version's compiled rules exceed ${MAX_SNAPSHOT_BYTES / 1024} KB, or if its effective end has already passed (P-058); move the end into the future with lexq_versions_update first.`,
|
|
4129
4142
|
inputSchema: z6.object({
|
|
4130
4143
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4131
4144
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -4139,7 +4152,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4139
4152
|
{
|
|
4140
4153
|
title: "Deploy to Live",
|
|
4141
4154
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4142
|
-
description: "Deploy an ACTIVE (published) version to live traffic. Takes effect immediately. Versions whose effective start date has not arrived are rejected (P-037) \u2014 use lexq_deploy_schedule for those. Undefined facts do not block deployment
|
|
4155
|
+
description: "Deploy an ACTIVE (published) version to live traffic. Takes effect immediately. Versions whose effective start date has not arrived are rejected (P-037) \u2014 use lexq_deploy_schedule for those. Versions whose effective end has passed are rejected (P-058). Undefined facts do not block deployment; use lexq_facts_unregistered to review what the version references but has not defined.",
|
|
4143
4156
|
inputSchema: z6.object({
|
|
4144
4157
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4145
4158
|
versionId: z6.string().uuid().describe("Version ID to deploy"),
|
|
@@ -4155,7 +4168,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4155
4168
|
{
|
|
4156
4169
|
title: "Rollback Deployment",
|
|
4157
4170
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4158
|
-
description: "Rollback to the previous deployed version. Only available if there is a previous version.",
|
|
4171
|
+
description: "Rollback to the previous deployed version. Only available if there is a previous version that is still ACTIVE and whose effective end has not passed (P-058).",
|
|
4159
4172
|
inputSchema: z6.object({
|
|
4160
4173
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4161
4174
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
package/dist/mcp/register.js
CHANGED
|
@@ -321,13 +321,15 @@ function registerVersionTools(server, callApi) {
|
|
|
321
321
|
{
|
|
322
322
|
title: "Update Policy Version",
|
|
323
323
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
324
|
-
description: "Update a DRAFT version. Only DRAFT versions can be modified.
|
|
324
|
+
description: "Update a DRAFT version. Only DRAFT versions can be modified. Omitted fields keep their current values. To remove an end of the effective window, set clearEffectiveFrom or clearEffectiveTo to true instead of sending a value.",
|
|
325
325
|
inputSchema: z3.object({
|
|
326
326
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
327
327
|
versionId: z3.string().uuid().describe("Version ID"),
|
|
328
328
|
commitMessage: z3.string().optional().describe("New commit message"),
|
|
329
|
-
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it
|
|
330
|
-
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it
|
|
329
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT} Omit it to keep the current start.`),
|
|
330
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT} Omit it to keep the current end.`),
|
|
331
|
+
clearEffectiveFrom: z3.boolean().optional().describe("Set to true to remove the effective start. Do not send effectiveFrom with it."),
|
|
332
|
+
clearEffectiveTo: z3.boolean().optional().describe("Set to true to remove the effective end. Do not send effectiveTo with it.")
|
|
331
333
|
})
|
|
332
334
|
},
|
|
333
335
|
async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
|
|
@@ -674,7 +676,7 @@ function registerFactTools(server, callApi) {
|
|
|
674
676
|
{
|
|
675
677
|
title: "List Unregistered Facts",
|
|
676
678
|
annotations: { readOnlyHint: true },
|
|
677
|
-
description: "List facts referenced by a version's rules but not yet defined (read-only \u2014 does not block publish/deploy
|
|
679
|
+
description: "List facts referenced by a version's rules but not yet defined (read-only \u2014 does not block publish/deploy). Version-wide: covers every rule in the version. Each entry carries the inferred type, suggested name, and where it is referenced (condition/action). Register them with lexq_facts_create to enable type validation and the dry-run requirements analyzer.",
|
|
678
680
|
inputSchema: z5.object({
|
|
679
681
|
groupId: z5.string().uuid().describe("Policy group ID"),
|
|
680
682
|
versionId: z5.string().uuid().describe("Version ID")
|
|
@@ -707,7 +709,7 @@ function registerDeployTools(server, callApi) {
|
|
|
707
709
|
{
|
|
708
710
|
title: "Publish Version",
|
|
709
711
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
710
|
-
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts referenced by rules do not block publishing
|
|
712
|
+
description: `Publish a DRAFT version (DRAFT \u2192 ACTIVE). Locks the version from further edits. Must have at least one rule. Undefined facts referenced by rules do not block publishing; call lexq_facts_unregistered first to review them. Publishing fails if the version's compiled rules exceed ${MAX_SNAPSHOT_BYTES / 1024} KB, or if its effective end has already passed (P-058); move the end into the future with lexq_versions_update first.`,
|
|
711
713
|
inputSchema: z6.object({
|
|
712
714
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
713
715
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -721,7 +723,7 @@ function registerDeployTools(server, callApi) {
|
|
|
721
723
|
{
|
|
722
724
|
title: "Deploy to Live",
|
|
723
725
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
724
|
-
description: "Deploy an ACTIVE (published) version to live traffic. Takes effect immediately. Versions whose effective start date has not arrived are rejected (P-037) \u2014 use lexq_deploy_schedule for those. Undefined facts do not block deployment
|
|
726
|
+
description: "Deploy an ACTIVE (published) version to live traffic. Takes effect immediately. Versions whose effective start date has not arrived are rejected (P-037) \u2014 use lexq_deploy_schedule for those. Versions whose effective end has passed are rejected (P-058). Undefined facts do not block deployment; use lexq_facts_unregistered to review what the version references but has not defined.",
|
|
725
727
|
inputSchema: z6.object({
|
|
726
728
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
727
729
|
versionId: z6.string().uuid().describe("Version ID to deploy"),
|
|
@@ -737,7 +739,7 @@ function registerDeployTools(server, callApi) {
|
|
|
737
739
|
{
|
|
738
740
|
title: "Rollback Deployment",
|
|
739
741
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
740
|
-
description: "Rollback to the previous deployed version. Only available if there is a previous version.",
|
|
742
|
+
description: "Rollback to the previous deployed version. Only available if there is a previous version that is still ACTIVE and whose effective end has not passed (P-058).",
|
|
741
743
|
inputSchema: z6.object({
|
|
742
744
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
743
745
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lexq/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"mcpName": "io.lexq/lexq",
|
|
5
5
|
"description": "LexQ CLI — manage policies, simulate rules, and deploy from the terminal. Built for humans and AI agents.",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"test:decimals": "pnpm build && node tests/exact-decimals.mjs",
|
|
42
42
|
"test:fact-key": "node tests/fact-key.mjs",
|
|
43
43
|
"test:export-format": "node tests/export-format.mjs",
|
|
44
|
+
"test:command-options": "node tests/command-options.mjs",
|
|
44
45
|
"test:value-domain": "pnpm build && node tests/value-domain.mjs",
|
|
45
46
|
"prepublishOnly": "pnpm build",
|
|
46
47
|
"knip": "knip",
|
|
@@ -253,7 +253,7 @@ lexq profile <groupId>
|
|
|
253
253
|
lexq profile <groupId> --rule <ruleId>
|
|
254
254
|
|
|
255
255
|
# Defaults: live version, last 24h, cache HIT
|
|
256
|
-
lexq profile <groupId> --version <vid> --cache MISS \
|
|
256
|
+
lexq profile <groupId> --version-id <vid> --cache MISS \
|
|
257
257
|
--from 2026-07-01T00:00:00Z --to 2026-08-01T00:00:00Z
|
|
258
258
|
```
|
|
259
259
|
|