@lexq/cli 1.0.1 → 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 +108 -62
- package/dist/mcp/register.js +42 -28
- package/package.json +2 -1
- package/skills/lexq-execution/SKILL.md +18 -7
- package/skills/lexq-recipes/SKILL.md +4 -0
- package/skills/lexq-shared/SKILL.md +6 -1
- package/skills/lexq-simulation/SKILL.md +3 -0
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
|
@@ -269,7 +269,7 @@ function registerAuthCommands(program) {
|
|
|
269
269
|
|
|
270
270
|
Example:
|
|
271
271
|
$ lexq auth whoami
|
|
272
|
-
{ "tenantId": "abc-123", "userId": "...", "role": "ADMIN", "apiKey": "lexq_us_****abcd" }
|
|
272
|
+
{ "tenantId": "abc-123", "userId": "...", "role": "ADMIN", "tenantTimezone": "Asia/Seoul", "apiKey": "lexq_us_****abcd" }
|
|
273
273
|
`
|
|
274
274
|
).action(async () => {
|
|
275
275
|
try {
|
|
@@ -310,9 +310,10 @@ function registerStatusCommand(program) {
|
|
|
310
310
|
|
|
311
311
|
Example:
|
|
312
312
|
$ lexq status
|
|
313
|
-
{ "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "ADMIN" }
|
|
313
|
+
{ "status": "ok", "latencyMs": 142, "tenantId": "abc-123", "role": "ADMIN", "tenantTimezone": "Asia/Seoul" }
|
|
314
314
|
|
|
315
315
|
Use this to verify your API key is valid and the LexQ API is reachable.
|
|
316
|
+
tenantTimezone is the zone every yyyy-MM-dd date option is read in.
|
|
316
317
|
`
|
|
317
318
|
).action(async () => {
|
|
318
319
|
const globalOpts = program.opts();
|
|
@@ -326,7 +327,8 @@ function registerStatusCommand(program) {
|
|
|
326
327
|
status: "ok",
|
|
327
328
|
latencyMs: Date.now() - startTime,
|
|
328
329
|
tenantId: info.tenantId,
|
|
329
|
-
role: info.role
|
|
330
|
+
role: info.role,
|
|
331
|
+
tenantTimezone: info.tenantTimezone
|
|
330
332
|
});
|
|
331
333
|
} catch (error) {
|
|
332
334
|
printError(error);
|
|
@@ -357,6 +359,22 @@ var MAX_SNAPSHOT_BYTES = 131072;
|
|
|
357
359
|
// src/commands/groups.ts
|
|
358
360
|
import "commander";
|
|
359
361
|
import dedent3 from "dedent";
|
|
362
|
+
|
|
363
|
+
// src/lib/dates.ts
|
|
364
|
+
var ORG_DAY = "yyyy-MM-dd, a whole calendar day in the organization time zone (tenantTimezone in whoami), not UTC";
|
|
365
|
+
var START_DAY = `Start date, ${ORG_DAY}. Inclusive.`;
|
|
366
|
+
var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
367
|
+
var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
|
|
368
|
+
var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
369
|
+
var INSTANT = "ISO-8601 instant with Z or an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+09:00. A bare date or a time without an offset is rejected.";
|
|
370
|
+
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
371
|
+
var STATS_MAX_WINDOW_DAYS = 31;
|
|
372
|
+
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
373
|
+
function utcMinute(instant) {
|
|
374
|
+
return instant ? `${new Date(instant).toISOString().substring(0, 16)}Z` : "\u2013";
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/commands/groups.ts
|
|
360
378
|
function registerGroupCommands(program) {
|
|
361
379
|
const groups = program.command("groups").description("Manage policy groups").addHelpText(
|
|
362
380
|
"after",
|
|
@@ -396,7 +414,7 @@ function registerGroupCommands(program) {
|
|
|
396
414
|
g.status,
|
|
397
415
|
String(g.priority),
|
|
398
416
|
g.currentVersionName ?? "\u2013",
|
|
399
|
-
g.updatedAt
|
|
417
|
+
utcMinute(g.updatedAt)
|
|
400
418
|
]),
|
|
401
419
|
{ truncate: 24 }
|
|
402
420
|
);
|
|
@@ -714,7 +732,7 @@ function registerVersionCommands(program) {
|
|
|
714
732
|
v.status,
|
|
715
733
|
v.commitMessage ?? "\u2013",
|
|
716
734
|
v.snapshotHash ? v.snapshotHash.substring(0, 8) : "\u2013",
|
|
717
|
-
v.createdAt
|
|
735
|
+
utcMinute(v.createdAt)
|
|
718
736
|
]),
|
|
719
737
|
{ truncate: 24 }
|
|
720
738
|
);
|
|
@@ -747,7 +765,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
747
765
|
process.exit(1);
|
|
748
766
|
}
|
|
749
767
|
});
|
|
750
|
-
versions.command("create").description("Create a new draft version").requiredOption("--group-id <groupId>", "Policy group ID").option("--commit-message <message>", "Commit message").option("--effective-from <
|
|
768
|
+
versions.command("create").description("Create a new draft version").requiredOption("--group-id <groupId>", "Policy group 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(
|
|
751
769
|
"after",
|
|
752
770
|
dedent4`
|
|
753
771
|
|
|
@@ -762,8 +780,8 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
762
780
|
|
|
763
781
|
Fields:
|
|
764
782
|
commitMessage string Version description (optional, max 255 chars)
|
|
765
|
-
effectiveFrom
|
|
766
|
-
effectiveTo
|
|
783
|
+
effectiveFrom instant Start of effective period (optional, with Z or an offset)
|
|
784
|
+
effectiveTo instant End of effective period (optional, auto-expires)
|
|
767
785
|
`
|
|
768
786
|
).action(async (opts) => {
|
|
769
787
|
try {
|
|
@@ -786,14 +804,18 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
786
804
|
process.exit(1);
|
|
787
805
|
}
|
|
788
806
|
});
|
|
789
|
-
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 <
|
|
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(
|
|
790
808
|
"after",
|
|
791
809
|
dedent4`
|
|
792
810
|
|
|
793
811
|
Only DRAFT versions can be updated. Published (ACTIVE) versions are immutable.
|
|
794
812
|
|
|
795
|
-
|
|
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.
|
|
815
|
+
|
|
816
|
+
Examples:
|
|
796
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
|
|
797
819
|
`
|
|
798
820
|
).action(async (opts) => {
|
|
799
821
|
try {
|
|
@@ -889,6 +911,8 @@ function buildUpdateBody(opts) {
|
|
|
889
911
|
if (opts.commitMessage) body.commitMessage = opts.commitMessage;
|
|
890
912
|
if (opts.effectiveFrom) body.effectiveFrom = opts.effectiveFrom;
|
|
891
913
|
if (opts.effectiveTo) body.effectiveTo = opts.effectiveTo;
|
|
914
|
+
if (opts.clearEffectiveFrom) body.clearEffectiveFrom = true;
|
|
915
|
+
if (opts.clearEffectiveTo) body.clearEffectiveTo = true;
|
|
892
916
|
return body;
|
|
893
917
|
}
|
|
894
918
|
|
|
@@ -1620,6 +1644,7 @@ function registerDeployCommands(program) {
|
|
|
1620
1644
|
|
|
1621
1645
|
Locks the version permanently. Rules cannot be modified after publishing.
|
|
1622
1646
|
A snapshot hash is generated for integrity verification.
|
|
1647
|
+
A version whose effective end has already passed cannot be published (P-058).
|
|
1623
1648
|
|
|
1624
1649
|
Example:
|
|
1625
1650
|
$ lexq deploy publish --group-id <gid> --version-id <vid> --memo "Validated via dry-run"
|
|
@@ -1650,6 +1675,8 @@ function registerDeployCommands(program) {
|
|
|
1650
1675
|
dedent7`
|
|
1651
1676
|
|
|
1652
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.
|
|
1653
1680
|
|
|
1654
1681
|
Example:
|
|
1655
1682
|
$ lexq deploy live --group-id <gid> --version-id <vid> --memo "Go live — v3"
|
|
@@ -1676,7 +1703,8 @@ function registerDeployCommands(program) {
|
|
|
1676
1703
|
dedent7`
|
|
1677
1704
|
|
|
1678
1705
|
Reverts to the version that was live before the current one.
|
|
1679
|
-
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).
|
|
1680
1708
|
|
|
1681
1709
|
Example:
|
|
1682
1710
|
$ lexq deploy rollback --group-id <gid> --memo "High error rate" --force
|
|
@@ -1742,7 +1770,7 @@ function registerDeployCommands(program) {
|
|
|
1742
1770
|
process.exit(1);
|
|
1743
1771
|
}
|
|
1744
1772
|
});
|
|
1745
|
-
deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", "Filter by types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)").option("--start-date <date>",
|
|
1773
|
+
deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option("--types <types>", "Filter by types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
1746
1774
|
"after",
|
|
1747
1775
|
dedent7`
|
|
1748
1776
|
|
|
@@ -1774,7 +1802,7 @@ function registerDeployCommands(program) {
|
|
|
1774
1802
|
d.policyGroupName,
|
|
1775
1803
|
d.versionNo != null ? `v${d.versionNo}` : "\u2013",
|
|
1776
1804
|
d.deployedByName,
|
|
1777
|
-
d.deployedAt
|
|
1805
|
+
utcMinute(d.deployedAt)
|
|
1778
1806
|
]),
|
|
1779
1807
|
{ truncate: 20 }
|
|
1780
1808
|
);
|
|
@@ -1836,7 +1864,7 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
1836
1864
|
d.groupName,
|
|
1837
1865
|
d.groupStatus,
|
|
1838
1866
|
d.currentVersionName ?? "\u2013",
|
|
1839
|
-
d.lastDeployedAt
|
|
1867
|
+
utcMinute(d.lastDeployedAt)
|
|
1840
1868
|
])
|
|
1841
1869
|
);
|
|
1842
1870
|
} else {
|
|
@@ -1903,6 +1931,8 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
1903
1931
|
|
|
1904
1932
|
The version must be ACTIVE with a future effective start date; the system
|
|
1905
1933
|
deploys it automatically at that time (within one scheduler tick, ≤60s).
|
|
1934
|
+
That time is the version's effectiveFrom, set on the DRAFT before publishing;
|
|
1935
|
+
this command takes no time of its own.
|
|
1906
1936
|
One pending schedule per group. Manual deploy/rollback/undeploy, starting
|
|
1907
1937
|
an A/B test, or archiving the group cancels the pending schedule.
|
|
1908
1938
|
|
|
@@ -1994,9 +2024,9 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
1994
2024
|
s.status,
|
|
1995
2025
|
s.policyGroupName ?? s.policyGroupId.substring(0, 8),
|
|
1996
2026
|
s.versionNo != null ? `v${s.versionNo}` : "\u2013",
|
|
1997
|
-
s.scheduledFor
|
|
2027
|
+
utcMinute(s.scheduledFor),
|
|
1998
2028
|
s.scheduledByName,
|
|
1999
|
-
s.status === "EXECUTED" ? s.executedAt
|
|
2029
|
+
s.status === "EXECUTED" ? utcMinute(s.executedAt) : s.status === "CANCELED" ? s.canceledReason ?? "\u2013" : s.status === "FAILED" ? s.failedReason ?? "\u2013" : "\u2013"
|
|
2000
2030
|
]),
|
|
2001
2031
|
{ truncate: 20 }
|
|
2002
2032
|
);
|
|
@@ -2177,7 +2207,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2177
2207
|
Use --format table for summary view.
|
|
2178
2208
|
`
|
|
2179
2209
|
);
|
|
2180
|
-
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(
|
|
2181
2211
|
"after",
|
|
2182
2212
|
dedent8`
|
|
2183
2213
|
|
|
@@ -2196,13 +2226,19 @@ function registerAnalyticsCommands(program) {
|
|
|
2196
2226
|
"maxRecords": 1000
|
|
2197
2227
|
}
|
|
2198
2228
|
}'
|
|
2199
|
-
|
|
2229
|
+
|
|
2230
|
+
$ lexq analytics simulation start --file simulation.json
|
|
2231
|
+
|
|
2200
2232
|
Dataset types: HISTORICAL, UPLOADED, MANUAL
|
|
2201
2233
|
Dataset sources: EXECUTION_LOGS, S3_BUCKET, REQUEST_BODY
|
|
2234
|
+
HISTORICAL from / to are yyyy-MM-dd, whole days in the organization time zone, both inclusive.
|
|
2202
2235
|
`
|
|
2203
2236
|
).action(async (opts) => {
|
|
2204
2237
|
try {
|
|
2205
2238
|
const globalOpts = program.opts();
|
|
2239
|
+
if (!opts.json && !opts.file) {
|
|
2240
|
+
throw new Error("Request body is required. Use --json or --file.");
|
|
2241
|
+
}
|
|
2206
2242
|
const body = resolveBody(opts);
|
|
2207
2243
|
const data = await apiRequest("POST", "analytics/simulations", {
|
|
2208
2244
|
apiKey: globalOpts.apiKey,
|
|
@@ -2315,7 +2351,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2315
2351
|
process.exit(1);
|
|
2316
2352
|
}
|
|
2317
2353
|
});
|
|
2318
|
-
sim.command("list").description("List simulation history").option("--status <status>", "Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELED)").option("--from <date>",
|
|
2354
|
+
sim.command("list").description("List simulation history").option("--status <status>", "Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELED)").option("--from <date>", START_DAY).option("--to <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").action(async (opts) => {
|
|
2319
2355
|
try {
|
|
2320
2356
|
const globalOpts = program.opts();
|
|
2321
2357
|
const format = globalOpts.format ?? "json";
|
|
@@ -2344,7 +2380,7 @@ function registerAnalyticsCommands(program) {
|
|
|
2344
2380
|
s.status,
|
|
2345
2381
|
`${s.matchRate.toFixed(1)}%`,
|
|
2346
2382
|
String(s.totalRecords),
|
|
2347
|
-
s.createdAt
|
|
2383
|
+
utcMinute(s.createdAt)
|
|
2348
2384
|
]),
|
|
2349
2385
|
{ truncate: 20 }
|
|
2350
2386
|
);
|
|
@@ -2553,7 +2589,7 @@ function registerProfileCommands(program) {
|
|
|
2553
2589
|
program.command("profile <groupId>").description("Per-rule latency profile with relative slow-rule flags").option(
|
|
2554
2590
|
"--rule <ruleId>",
|
|
2555
2591
|
`Single-rule detail (distributions + ${LATENCY_WINDOW_MILLIS / 1e3}s window series)`
|
|
2556
|
-
).option("--version <versionId>", "Version to inspect (default: live version)").option(
|
|
2592
|
+
).option("--version-id <versionId>", "Version to inspect (default: live version)").option(
|
|
2557
2593
|
"--from <instant>",
|
|
2558
2594
|
`Window start, ISO-8601 instant (default: ${PROFILE_DEFAULT_WINDOW_HOURS}h ago)`
|
|
2559
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(
|
|
@@ -2569,7 +2605,7 @@ function registerProfileCommands(program) {
|
|
|
2569
2605
|
Examples:
|
|
2570
2606
|
$ lexq profile <groupId>
|
|
2571
2607
|
$ lexq profile <groupId> --cache MISS --from 2026-07-01T00:00:00Z
|
|
2572
|
-
$ lexq profile <groupId> --rule <ruleId> --version <versionId>
|
|
2608
|
+
$ lexq profile <groupId> --rule <ruleId> --version-id <versionId>
|
|
2573
2609
|
`
|
|
2574
2610
|
).action(async (groupId, opts) => {
|
|
2575
2611
|
try {
|
|
@@ -2579,7 +2615,7 @@ function registerProfileCommands(program) {
|
|
|
2579
2615
|
throw new Error(`--cache must be one of: ${ProfileCacheState.join(" | ")}`);
|
|
2580
2616
|
}
|
|
2581
2617
|
const params = {};
|
|
2582
|
-
if (opts.
|
|
2618
|
+
if (opts.versionId) params.versionId = opts.versionId;
|
|
2583
2619
|
if (opts.from) params.from = opts.from;
|
|
2584
2620
|
if (opts.to) params.to = opts.to;
|
|
2585
2621
|
if (opts.cache) params.cacheState = opts.cache;
|
|
@@ -2665,7 +2701,7 @@ function registerHistoryCommands(program) {
|
|
|
2665
2701
|
Statuses: SUCCESS, NO_MATCH, ERROR, TIMEOUT
|
|
2666
2702
|
`
|
|
2667
2703
|
);
|
|
2668
|
-
history.command("list").description("List execution history").option("--trace-id <traceId>", "Filter by trace ID").option("--group-id <groupId>", "Filter by policy group").option("--version-id <versionId>", "Filter by version").option("--status <status>", "Filter by status (SUCCESS, NO_MATCH, ERROR, TIMEOUT)").option("--start-date <date>",
|
|
2704
|
+
history.command("list").description("List execution history").option("--trace-id <traceId>", "Filter by trace ID").option("--group-id <groupId>", "Filter by policy group").option("--version-id <versionId>", "Filter by version").option("--status <status>", "Filter by status (SUCCESS, NO_MATCH, ERROR, TIMEOUT)").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
2669
2705
|
"after",
|
|
2670
2706
|
dedent10`
|
|
2671
2707
|
|
|
@@ -2705,7 +2741,7 @@ function registerHistoryCommands(program) {
|
|
|
2705
2741
|
h.status,
|
|
2706
2742
|
h.isMatched ? "\u2713" : "\u2717",
|
|
2707
2743
|
`${h.latencyMs}ms`,
|
|
2708
|
-
h.createdAt
|
|
2744
|
+
utcMinute(h.createdAt)
|
|
2709
2745
|
]),
|
|
2710
2746
|
{ truncate: 20 }
|
|
2711
2747
|
);
|
|
@@ -2745,15 +2781,19 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
|
|
|
2745
2781
|
process.exit(1);
|
|
2746
2782
|
}
|
|
2747
2783
|
});
|
|
2748
|
-
history.command("stats").description("Get execution statistics").option("--group-id <groupId>", "Filter by policy group").option("--start-date <date>",
|
|
2784
|
+
history.command("stats").description("Get execution statistics").option("--group-id <groupId>", "Filter by policy group").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).addHelpText(
|
|
2749
2785
|
"after",
|
|
2750
2786
|
dedent10`
|
|
2751
2787
|
|
|
2752
2788
|
Shows total executions, success/no-match/failure counts, success rate, and avg latency.
|
|
2753
2789
|
|
|
2790
|
+
Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization
|
|
2791
|
+
time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039.
|
|
2792
|
+
--start-date alone runs through today.
|
|
2793
|
+
|
|
2754
2794
|
Example:
|
|
2755
2795
|
$ lexq history stats --format table
|
|
2756
|
-
$ lexq history stats --group-id <gid> --start-date 2026-04-01
|
|
2796
|
+
$ lexq history stats --group-id <gid> --start-date 2026-04-01 --end-date 2026-04-30
|
|
2757
2797
|
`
|
|
2758
2798
|
).action(async (opts) => {
|
|
2759
2799
|
try {
|
|
@@ -2840,7 +2880,7 @@ function registerReplayCommands(program) {
|
|
|
2840
2880
|
process.exit(1);
|
|
2841
2881
|
}
|
|
2842
2882
|
});
|
|
2843
|
-
replay.command("start").description("Submit a window replay job (blast radius)").requiredOption("--version-id <versionId>", "Candidate version to re-evaluate against").requiredOption("--from <date>",
|
|
2883
|
+
replay.command("start").description("Submit a window replay job (blast radius)").requiredOption("--version-id <versionId>", "Candidate version to re-evaluate against").requiredOption("--from <date>", WINDOW_START_DAY).requiredOption("--to <date>", WINDOW_END_DAY).option("--max-records <number>", `Sample cap (hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`).addHelpText(
|
|
2844
2884
|
"after",
|
|
2845
2885
|
dedent11`
|
|
2846
2886
|
|
|
@@ -2884,7 +2924,7 @@ function registerReplayCommands(program) {
|
|
|
2884
2924
|
});
|
|
2885
2925
|
if (format === "table") {
|
|
2886
2926
|
printTable(
|
|
2887
|
-
["Job", "Version", "Window", "Status", "Progress", "Changed", "At"],
|
|
2927
|
+
["Job", "Version", "Window (org time zone)", "Status", "Progress", "Changed", "At"],
|
|
2888
2928
|
data.content.map((j) => [
|
|
2889
2929
|
j.jobId.substring(0, 12),
|
|
2890
2930
|
j.candidateVersionName ?? "\u2013",
|
|
@@ -2892,7 +2932,7 @@ function registerReplayCommands(program) {
|
|
|
2892
2932
|
j.status,
|
|
2893
2933
|
`${j.progress}%`,
|
|
2894
2934
|
`${j.changedCount}${j.capped ? " (capped)" : ""}`,
|
|
2895
|
-
j.createdAt
|
|
2935
|
+
utcMinute(j.createdAt)
|
|
2896
2936
|
]),
|
|
2897
2937
|
{ truncate: 24 }
|
|
2898
2938
|
);
|
|
@@ -2998,7 +3038,7 @@ function registerProvenanceCommands(program) {
|
|
|
2998
3038
|
process.exit(1);
|
|
2999
3039
|
}
|
|
3000
3040
|
});
|
|
3001
|
-
provenance.command("reveal-audits").description("List PII reveal audits (who revealed what, when)").option("--trace-id <traceId>", "Filter by trace ID (exact)").option("--fact-key <factKey>", "Filter by fact key (partial, case-insensitive)").option("--revealed-by <operatorId>", "Filter by operator ID (exact)").option("--start-date <date>",
|
|
3041
|
+
provenance.command("reveal-audits").description("List PII reveal audits (who revealed what, when)").option("--trace-id <traceId>", "Filter by trace ID (exact)").option("--fact-key <factKey>", "Filter by fact key (partial, case-insensitive)").option("--revealed-by <operatorId>", "Filter by operator ID (exact)").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
3002
3042
|
"after",
|
|
3003
3043
|
dedent12`
|
|
3004
3044
|
|
|
@@ -3032,7 +3072,7 @@ function registerProvenanceCommands(program) {
|
|
|
3032
3072
|
printTable(
|
|
3033
3073
|
["Revealed At", "By", "Fact Key", "Trace"],
|
|
3034
3074
|
data.content.map((a) => [
|
|
3035
|
-
a.revealedAt
|
|
3075
|
+
utcMinute(a.revealedAt),
|
|
3036
3076
|
a.revealedByName,
|
|
3037
3077
|
a.factKey,
|
|
3038
3078
|
a.traceId.substring(0, 12)
|
|
@@ -3070,10 +3110,13 @@ function registerLogCommands(program) {
|
|
|
3070
3110
|
Statuses: PENDING (needs attention), RESOLVED, IGNORED
|
|
3071
3111
|
`
|
|
3072
3112
|
);
|
|
3073
|
-
logs.command("list").description("List failure logs").option("--task-type <taskType>", `Filter by task type (${TaskType.join(", ")})`).option("--status <status>", "Filter by status (PENDING, RESOLVED, IGNORED)").option("--keyword <keyword>", "Search keyword").option("--start-date <date>",
|
|
3113
|
+
logs.command("list").description("List failure logs").option("--task-type <taskType>", `Filter by task type (${TaskType.join(", ")})`).option("--status <status>", "Filter by status (PENDING, RESOLVED, IGNORED)").option("--keyword <keyword>", "Search keyword").option("--start-date <date>", START_DAY).option("--end-date <date>", END_DAY).option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
|
|
3074
3114
|
"after",
|
|
3075
3115
|
dedent13`
|
|
3076
3116
|
|
|
3117
|
+
Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization
|
|
3118
|
+
time zone. Pass --start-date to look further back.
|
|
3119
|
+
|
|
3077
3120
|
Examples:
|
|
3078
3121
|
$ lexq logs list --status PENDING --format table
|
|
3079
3122
|
$ lexq logs list --task-type PLATFORM_WEBHOOK
|
|
@@ -3104,7 +3147,7 @@ function registerLogCommands(program) {
|
|
|
3104
3147
|
l.taskType,
|
|
3105
3148
|
l.status,
|
|
3106
3149
|
l.errorMessage?.substring(0, 24) ?? "\u2013",
|
|
3107
|
-
l.createdAt
|
|
3150
|
+
utcMinute(l.createdAt)
|
|
3108
3151
|
]),
|
|
3109
3152
|
{ truncate: 24 }
|
|
3110
3153
|
);
|
|
@@ -3502,7 +3545,7 @@ function registerStatusTools(server, callApi) {
|
|
|
3502
3545
|
{
|
|
3503
3546
|
title: "Who Am I",
|
|
3504
3547
|
annotations: { readOnlyHint: true },
|
|
3505
|
-
description:
|
|
3548
|
+
description: 'Show current authentication info (tenant ID, user ID, role) and tenantTimezone, the organization time zone that every yyyy-MM-dd date filter uses. Work out "today" or "yesterday" in that zone, not in UTC.',
|
|
3506
3549
|
inputSchema: z.object({})
|
|
3507
3550
|
},
|
|
3508
3551
|
async () => callApi("GET", "whoami")
|
|
@@ -3700,8 +3743,8 @@ function registerVersionTools(server, callApi) {
|
|
|
3700
3743
|
inputSchema: z3.object({
|
|
3701
3744
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
3702
3745
|
commitMessage: z3.string().optional().describe("Commit message describing this version"),
|
|
3703
|
-
effectiveFrom: z3.string().optional().describe(
|
|
3704
|
-
effectiveTo: z3.string().optional().describe(
|
|
3746
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT}`),
|
|
3747
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT}`)
|
|
3705
3748
|
})
|
|
3706
3749
|
},
|
|
3707
3750
|
async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/versions`, { body })
|
|
@@ -3711,13 +3754,15 @@ function registerVersionTools(server, callApi) {
|
|
|
3711
3754
|
{
|
|
3712
3755
|
title: "Update Policy Version",
|
|
3713
3756
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
3714
|
-
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.",
|
|
3715
3758
|
inputSchema: z3.object({
|
|
3716
3759
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
3717
3760
|
versionId: z3.string().uuid().describe("Version ID"),
|
|
3718
3761
|
commitMessage: z3.string().optional().describe("New commit message"),
|
|
3719
|
-
effectiveFrom: z3.string().optional().describe(
|
|
3720
|
-
effectiveTo: z3.string().optional().describe(
|
|
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.")
|
|
3721
3766
|
})
|
|
3722
3767
|
},
|
|
3723
3768
|
async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
|
|
@@ -4060,7 +4105,7 @@ function registerFactTools(server, callApi) {
|
|
|
4060
4105
|
{
|
|
4061
4106
|
title: "List Unregistered Facts",
|
|
4062
4107
|
annotations: { readOnlyHint: true },
|
|
4063
|
-
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.",
|
|
4064
4109
|
inputSchema: z5.object({
|
|
4065
4110
|
groupId: z5.string().uuid().describe("Policy group ID"),
|
|
4066
4111
|
versionId: z5.string().uuid().describe("Version ID")
|
|
@@ -4093,7 +4138,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4093
4138
|
{
|
|
4094
4139
|
title: "Publish Version",
|
|
4095
4140
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4096
|
-
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.`,
|
|
4097
4142
|
inputSchema: z6.object({
|
|
4098
4143
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4099
4144
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -4107,7 +4152,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4107
4152
|
{
|
|
4108
4153
|
title: "Deploy to Live",
|
|
4109
4154
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4110
|
-
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.",
|
|
4111
4156
|
inputSchema: z6.object({
|
|
4112
4157
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4113
4158
|
versionId: z6.string().uuid().describe("Version ID to deploy"),
|
|
@@ -4123,7 +4168,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4123
4168
|
{
|
|
4124
4169
|
title: "Rollback Deployment",
|
|
4125
4170
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
4126
|
-
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).",
|
|
4127
4172
|
inputSchema: z6.object({
|
|
4128
4173
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4129
4174
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
|
@@ -4153,7 +4198,7 @@ function registerDeployTools(server, callApi) {
|
|
|
4153
4198
|
{
|
|
4154
4199
|
title: "Schedule Deployment",
|
|
4155
4200
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
4156
|
-
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
4201
|
+
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). The time is the version's effectiveFrom, set on the DRAFT before publishing; this call takes no time of its own. One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
4157
4202
|
inputSchema: z6.object({
|
|
4158
4203
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
4159
4204
|
versionId: z6.string().uuid().describe("ACTIVE version ID with a future effective start date"),
|
|
@@ -4200,8 +4245,8 @@ function registerDeployTools(server, callApi) {
|
|
|
4200
4245
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4201
4246
|
groupId: z6.string().uuid().optional().describe("Filter by group ID"),
|
|
4202
4247
|
types: z6.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
|
|
4203
|
-
startDate: z6.string().optional().describe(
|
|
4204
|
-
endDate: z6.string().optional().describe(
|
|
4248
|
+
startDate: z6.string().optional().describe(START_DAY),
|
|
4249
|
+
endDate: z6.string().optional().describe(END_DAY)
|
|
4205
4250
|
})
|
|
4206
4251
|
},
|
|
4207
4252
|
async ({ page, size, groupId, types, startDate, endDate }) => {
|
|
@@ -4345,7 +4390,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4345
4390
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
4346
4391
|
|
|
4347
4392
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
4348
|
-
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd
|
|
4393
|
+
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd, whole days
|
|
4394
|
+
in the organization time zone, both inclusive)
|
|
4349
4395
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
4350
4396
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
4351
4397
|
|
|
@@ -4400,8 +4446,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
4400
4446
|
page: z7.number().int().min(0).default(0).describe("Page number"),
|
|
4401
4447
|
size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4402
4448
|
status: z7.enum(["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"]).optional().describe("Filter by status"),
|
|
4403
|
-
from: z7.string().optional().describe(
|
|
4404
|
-
to: z7.string().optional().describe(
|
|
4449
|
+
from: z7.string().optional().describe(START_DAY),
|
|
4450
|
+
to: z7.string().optional().describe(END_DAY)
|
|
4405
4451
|
})
|
|
4406
4452
|
},
|
|
4407
4453
|
async ({ page, size, status, from, to }) => {
|
|
@@ -4579,8 +4625,8 @@ function registerReplayTools(server, callApi) {
|
|
|
4579
4625
|
description: "Submit an async job that replays a date window of past executions against a candidate version and measures the blast radius (how many decisions change). Billed per replayed record (REPLAY metric); VIEWER role cannot submit. Poll with lexq_replay_status.",
|
|
4580
4626
|
inputSchema: z9.object({
|
|
4581
4627
|
candidateVersionId: z9.string().uuid().describe("Version to re-evaluate against"),
|
|
4582
|
-
from: z9.string().describe(
|
|
4583
|
-
to: z9.string().describe(
|
|
4628
|
+
from: z9.string().describe(WINDOW_START_DAY),
|
|
4629
|
+
to: z9.string().describe(WINDOW_END_DAY),
|
|
4584
4630
|
maxRecords: z9.number().int().min(1).optional().describe(
|
|
4585
4631
|
`Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
|
|
4586
4632
|
)
|
|
@@ -4656,8 +4702,8 @@ function registerHistoryTools(server, callApi) {
|
|
|
4656
4702
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
4657
4703
|
versionId: z10.string().uuid().optional().describe("Filter by version"),
|
|
4658
4704
|
status: z10.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
|
|
4659
|
-
startDate: z10.string().optional().describe(
|
|
4660
|
-
endDate: z10.string().optional().describe(
|
|
4705
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
4706
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
4661
4707
|
})
|
|
4662
4708
|
},
|
|
4663
4709
|
async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
|
|
@@ -4688,11 +4734,11 @@ function registerHistoryTools(server, callApi) {
|
|
|
4688
4734
|
{
|
|
4689
4735
|
title: "Execution Statistics",
|
|
4690
4736
|
annotations: { readOnlyHint: true },
|
|
4691
|
-
description:
|
|
4737
|
+
description: `Get execution KPIs: total executions, success/failure counts, success rate, and average latency. Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039. startDate alone runs through today.`,
|
|
4692
4738
|
inputSchema: z10.object({
|
|
4693
4739
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
4694
|
-
startDate: z10.string().optional().describe(
|
|
4695
|
-
endDate: z10.string().optional().describe(
|
|
4740
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
4741
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
4696
4742
|
})
|
|
4697
4743
|
},
|
|
4698
4744
|
async ({ groupId, startDate, endDate }) => {
|
|
@@ -4732,8 +4778,8 @@ function registerProvenanceTools(server, callApi) {
|
|
|
4732
4778
|
traceId: z11.string().optional().describe("Filter by trace ID (exact match)"),
|
|
4733
4779
|
revealedBy: z11.string().optional().describe("Filter by operator ID (exact match)"),
|
|
4734
4780
|
factKey: z11.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
|
|
4735
|
-
startDate: z11.string().optional().describe(
|
|
4736
|
-
endDate: z11.string().optional().describe(
|
|
4781
|
+
startDate: z11.string().optional().describe(START_DAY),
|
|
4782
|
+
endDate: z11.string().optional().describe(END_DAY)
|
|
4737
4783
|
})
|
|
4738
4784
|
},
|
|
4739
4785
|
async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
|
|
@@ -4756,15 +4802,15 @@ function registerLogTools(server, callApi) {
|
|
|
4756
4802
|
{
|
|
4757
4803
|
title: "List Failure Logs",
|
|
4758
4804
|
annotations: { readOnlyHint: true },
|
|
4759
|
-
description:
|
|
4805
|
+
description: `List system failure logs from background tasks (platform event webhooks, scheduled deployments). Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization time zone; pass startDate to look further back.`,
|
|
4760
4806
|
inputSchema: z12.object({
|
|
4761
4807
|
page: z12.number().int().min(0).default(0).describe("Page number"),
|
|
4762
4808
|
size: z12.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
4763
4809
|
taskType: z12.enum(TaskType).optional().describe("Task type"),
|
|
4764
4810
|
status: z12.enum(FailureStatus).optional().describe("Log status"),
|
|
4765
4811
|
keyword: z12.string().optional().describe("Search in refId, refSubId, errorMessage"),
|
|
4766
|
-
startDate: z12.string().optional().describe(
|
|
4767
|
-
endDate: z12.string().optional().describe(
|
|
4812
|
+
startDate: z12.string().optional().describe(START_DAY),
|
|
4813
|
+
endDate: z12.string().optional().describe(END_DAY)
|
|
4768
4814
|
})
|
|
4769
4815
|
},
|
|
4770
4816
|
async ({ page, size, taskType, status, keyword, startDate, endDate }) => {
|
package/dist/mcp/register.js
CHANGED
|
@@ -6,7 +6,7 @@ function registerStatusTools(server, callApi) {
|
|
|
6
6
|
{
|
|
7
7
|
title: "Who Am I",
|
|
8
8
|
annotations: { readOnlyHint: true },
|
|
9
|
-
description:
|
|
9
|
+
description: 'Show current authentication info (tenant ID, user ID, role) and tenantTimezone, the organization time zone that every yyyy-MM-dd date filter uses. Work out "today" or "yesterday" in that zone, not in UTC.',
|
|
10
10
|
inputSchema: z.object({})
|
|
11
11
|
},
|
|
12
12
|
async () => callApi("GET", "whoami")
|
|
@@ -259,6 +259,17 @@ function formatUnregisteredFactWarning(meta) {
|
|
|
259
259
|
].join("\n");
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
// src/lib/dates.ts
|
|
263
|
+
var ORG_DAY = "yyyy-MM-dd, a whole calendar day in the organization time zone (tenantTimezone in whoami), not UTC";
|
|
264
|
+
var START_DAY = `Start date, ${ORG_DAY}. Inclusive.`;
|
|
265
|
+
var END_DAY = `End date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
266
|
+
var WINDOW_START_DAY = `Window start date, ${ORG_DAY}. Inclusive.`;
|
|
267
|
+
var WINDOW_END_DAY = `Window end date, ${ORG_DAY}. Inclusive: the whole day counts.`;
|
|
268
|
+
var INSTANT = "ISO-8601 instant with Z or an offset, e.g. 2026-10-01T00:00:00Z or 2026-10-01T09:00:00+09:00. A bare date or a time without an offset is rejected.";
|
|
269
|
+
var STATS_DEFAULT_WINDOW_DAYS = 30;
|
|
270
|
+
var STATS_MAX_WINDOW_DAYS = 31;
|
|
271
|
+
var FAILURE_LOG_LOOKBACK_DAYS = 7;
|
|
272
|
+
|
|
262
273
|
// src/mcp/tools/versions.ts
|
|
263
274
|
function registerVersionTools(server, callApi) {
|
|
264
275
|
server.registerTool(
|
|
@@ -299,8 +310,8 @@ function registerVersionTools(server, callApi) {
|
|
|
299
310
|
inputSchema: z3.object({
|
|
300
311
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
301
312
|
commitMessage: z3.string().optional().describe("Commit message describing this version"),
|
|
302
|
-
effectiveFrom: z3.string().optional().describe(
|
|
303
|
-
effectiveTo: z3.string().optional().describe(
|
|
313
|
+
effectiveFrom: z3.string().optional().describe(`Effective start, ${INSTANT}`),
|
|
314
|
+
effectiveTo: z3.string().optional().describe(`Effective end, ${INSTANT}`)
|
|
304
315
|
})
|
|
305
316
|
},
|
|
306
317
|
async ({ groupId, ...body }) => callApi("POST", `policy-groups/${groupId}/versions`, { body })
|
|
@@ -310,13 +321,15 @@ function registerVersionTools(server, callApi) {
|
|
|
310
321
|
{
|
|
311
322
|
title: "Update Policy Version",
|
|
312
323
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
313
|
-
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.",
|
|
314
325
|
inputSchema: z3.object({
|
|
315
326
|
groupId: z3.string().uuid().describe("Policy group ID"),
|
|
316
327
|
versionId: z3.string().uuid().describe("Version ID"),
|
|
317
328
|
commitMessage: z3.string().optional().describe("New commit message"),
|
|
318
|
-
effectiveFrom: z3.string().optional().describe(
|
|
319
|
-
effectiveTo: z3.string().optional().describe(
|
|
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.")
|
|
320
333
|
})
|
|
321
334
|
},
|
|
322
335
|
async ({ groupId, versionId, ...body }) => callApi("PUT", `policy-groups/${groupId}/versions/${versionId}`, { body })
|
|
@@ -663,7 +676,7 @@ function registerFactTools(server, callApi) {
|
|
|
663
676
|
{
|
|
664
677
|
title: "List Unregistered Facts",
|
|
665
678
|
annotations: { readOnlyHint: true },
|
|
666
|
-
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.",
|
|
667
680
|
inputSchema: z5.object({
|
|
668
681
|
groupId: z5.string().uuid().describe("Policy group ID"),
|
|
669
682
|
versionId: z5.string().uuid().describe("Version ID")
|
|
@@ -696,7 +709,7 @@ function registerDeployTools(server, callApi) {
|
|
|
696
709
|
{
|
|
697
710
|
title: "Publish Version",
|
|
698
711
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
699
|
-
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.`,
|
|
700
713
|
inputSchema: z6.object({
|
|
701
714
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
702
715
|
versionId: z6.string().uuid().describe("Version ID to publish"),
|
|
@@ -710,7 +723,7 @@ function registerDeployTools(server, callApi) {
|
|
|
710
723
|
{
|
|
711
724
|
title: "Deploy to Live",
|
|
712
725
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
713
|
-
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.",
|
|
714
727
|
inputSchema: z6.object({
|
|
715
728
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
716
729
|
versionId: z6.string().uuid().describe("Version ID to deploy"),
|
|
@@ -726,7 +739,7 @@ function registerDeployTools(server, callApi) {
|
|
|
726
739
|
{
|
|
727
740
|
title: "Rollback Deployment",
|
|
728
741
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
729
|
-
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).",
|
|
730
743
|
inputSchema: z6.object({
|
|
731
744
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
732
745
|
memo: z6.string().min(1).describe("Rollback reason (required)")
|
|
@@ -756,7 +769,7 @@ function registerDeployTools(server, callApi) {
|
|
|
756
769
|
{
|
|
757
770
|
title: "Schedule Deployment",
|
|
758
771
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
759
|
-
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
772
|
+
description: "Schedule an ACTIVE version with a future effective start date to auto-deploy at that time (Scheduled Deployment). The time is the version's effectiveFrom, set on the DRAFT before publishing; this call takes no time of its own. One pending schedule per group; manual deploy/rollback/undeploy, starting an A/B test, or archiving the group cancels it. The snapshot hash is sealed at scheduling and re-verified at execution (fail-closed).",
|
|
760
773
|
inputSchema: z6.object({
|
|
761
774
|
groupId: z6.string().uuid().describe("Policy group ID"),
|
|
762
775
|
versionId: z6.string().uuid().describe("ACTIVE version ID with a future effective start date"),
|
|
@@ -803,8 +816,8 @@ function registerDeployTools(server, callApi) {
|
|
|
803
816
|
size: z6.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
804
817
|
groupId: z6.string().uuid().optional().describe("Filter by group ID"),
|
|
805
818
|
types: z6.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
|
|
806
|
-
startDate: z6.string().optional().describe(
|
|
807
|
-
endDate: z6.string().optional().describe(
|
|
819
|
+
startDate: z6.string().optional().describe(START_DAY),
|
|
820
|
+
endDate: z6.string().optional().describe(END_DAY)
|
|
808
821
|
})
|
|
809
822
|
},
|
|
810
823
|
async ({ page, size, groupId, types, startDate, endDate }) => {
|
|
@@ -948,7 +961,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
948
961
|
Start an Impact Simulation against historical, uploaded, or inline data.
|
|
949
962
|
|
|
950
963
|
dataset.type and dataset.source are BOTH required, and must be paired:
|
|
951
|
-
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd
|
|
964
|
+
HISTORICAL → source EXECUTION_LOGS, with dataset.from / dataset.to (yyyy-MM-dd, whole days
|
|
965
|
+
in the organization time zone, both inclusive)
|
|
952
966
|
UPLOADED → source S3_BUCKET, with dataset.path (the path returned by lexq_dataset_upload)
|
|
953
967
|
MANUAL → source REQUEST_BODY, with dataset.manualData (array of fact records)
|
|
954
968
|
|
|
@@ -1003,8 +1017,8 @@ function registerAnalyticsTools(server, callApi) {
|
|
|
1003
1017
|
page: z7.number().int().min(0).default(0).describe("Page number"),
|
|
1004
1018
|
size: z7.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
1005
1019
|
status: z7.enum(["PENDING", "RUNNING", "COMPLETED", "FAILED", "CANCELED"]).optional().describe("Filter by status"),
|
|
1006
|
-
from: z7.string().optional().describe(
|
|
1007
|
-
to: z7.string().optional().describe(
|
|
1020
|
+
from: z7.string().optional().describe(START_DAY),
|
|
1021
|
+
to: z7.string().optional().describe(END_DAY)
|
|
1008
1022
|
})
|
|
1009
1023
|
},
|
|
1010
1024
|
async ({ page, size, status, from, to }) => {
|
|
@@ -1182,8 +1196,8 @@ function registerReplayTools(server, callApi) {
|
|
|
1182
1196
|
description: "Submit an async job that replays a date window of past executions against a candidate version and measures the blast radius (how many decisions change). Billed per replayed record (REPLAY metric); VIEWER role cannot submit. Poll with lexq_replay_status.",
|
|
1183
1197
|
inputSchema: z9.object({
|
|
1184
1198
|
candidateVersionId: z9.string().uuid().describe("Version to re-evaluate against"),
|
|
1185
|
-
from: z9.string().describe(
|
|
1186
|
-
to: z9.string().describe(
|
|
1199
|
+
from: z9.string().describe(WINDOW_START_DAY),
|
|
1200
|
+
to: z9.string().describe(WINDOW_END_DAY),
|
|
1187
1201
|
maxRecords: z9.number().int().min(1).optional().describe(
|
|
1188
1202
|
`Sample cap (server default applies; hard cap ${REPLAY_WINDOW_MAX_RECORDS / 1e3}k)`
|
|
1189
1203
|
)
|
|
@@ -1259,8 +1273,8 @@ function registerHistoryTools(server, callApi) {
|
|
|
1259
1273
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
1260
1274
|
versionId: z10.string().uuid().optional().describe("Filter by version"),
|
|
1261
1275
|
status: z10.enum(["SUCCESS", "NO_MATCH", "ERROR", "TIMEOUT"]).optional().describe("Filter by execution status"),
|
|
1262
|
-
startDate: z10.string().optional().describe(
|
|
1263
|
-
endDate: z10.string().optional().describe(
|
|
1276
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
1277
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
1264
1278
|
})
|
|
1265
1279
|
},
|
|
1266
1280
|
async ({ page, size, traceId, groupId, versionId, status, startDate, endDate }) => {
|
|
@@ -1291,11 +1305,11 @@ function registerHistoryTools(server, callApi) {
|
|
|
1291
1305
|
{
|
|
1292
1306
|
title: "Execution Statistics",
|
|
1293
1307
|
annotations: { readOnlyHint: true },
|
|
1294
|
-
description:
|
|
1308
|
+
description: `Get execution KPIs: total executions, success/failure counts, success rate, and average latency. Without dates the window is the last ${STATS_DEFAULT_WINDOW_DAYS} days through today, in the organization time zone. At most ${STATS_MAX_WINDOW_DAYS} days per call; a longer range is rejected with AN-039. startDate alone runs through today.`,
|
|
1295
1309
|
inputSchema: z10.object({
|
|
1296
1310
|
groupId: z10.string().uuid().optional().describe("Filter by policy group"),
|
|
1297
|
-
startDate: z10.string().optional().describe(
|
|
1298
|
-
endDate: z10.string().optional().describe(
|
|
1311
|
+
startDate: z10.string().optional().describe(START_DAY),
|
|
1312
|
+
endDate: z10.string().optional().describe(END_DAY)
|
|
1299
1313
|
})
|
|
1300
1314
|
},
|
|
1301
1315
|
async ({ groupId, startDate, endDate }) => {
|
|
@@ -1335,8 +1349,8 @@ function registerProvenanceTools(server, callApi) {
|
|
|
1335
1349
|
traceId: z11.string().optional().describe("Filter by trace ID (exact match)"),
|
|
1336
1350
|
revealedBy: z11.string().optional().describe("Filter by operator ID (exact match)"),
|
|
1337
1351
|
factKey: z11.string().optional().describe("Filter by fact key (partial match, case-insensitive)"),
|
|
1338
|
-
startDate: z11.string().optional().describe(
|
|
1339
|
-
endDate: z11.string().optional().describe(
|
|
1352
|
+
startDate: z11.string().optional().describe(START_DAY),
|
|
1353
|
+
endDate: z11.string().optional().describe(END_DAY)
|
|
1340
1354
|
})
|
|
1341
1355
|
},
|
|
1342
1356
|
async ({ page, size, traceId, revealedBy, factKey, startDate, endDate }) => {
|
|
@@ -1359,15 +1373,15 @@ function registerLogTools(server, callApi) {
|
|
|
1359
1373
|
{
|
|
1360
1374
|
title: "List Failure Logs",
|
|
1361
1375
|
annotations: { readOnlyHint: true },
|
|
1362
|
-
description:
|
|
1376
|
+
description: `List system failure logs from background tasks (platform event webhooks, scheduled deployments). Without dates the window is ${FAILURE_LOG_LOOKBACK_DAYS} days back through today, in the organization time zone; pass startDate to look further back.`,
|
|
1363
1377
|
inputSchema: z12.object({
|
|
1364
1378
|
page: z12.number().int().min(0).default(0).describe("Page number"),
|
|
1365
1379
|
size: z12.number().int().min(1).max(100).default(20).describe("Page size"),
|
|
1366
1380
|
taskType: z12.enum(TaskType).optional().describe("Task type"),
|
|
1367
1381
|
status: z12.enum(FailureStatus).optional().describe("Log status"),
|
|
1368
1382
|
keyword: z12.string().optional().describe("Search in refId, refSubId, errorMessage"),
|
|
1369
|
-
startDate: z12.string().optional().describe(
|
|
1370
|
-
endDate: z12.string().optional().describe(
|
|
1383
|
+
startDate: z12.string().optional().describe(START_DAY),
|
|
1384
|
+
endDate: z12.string().optional().describe(END_DAY)
|
|
1371
1385
|
})
|
|
1372
1386
|
},
|
|
1373
1387
|
async ({ page, size, taskType, status, keyword, startDate, endDate }) => {
|
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",
|
|
@@ -23,8 +23,11 @@ Filter options:
|
|
|
23
23
|
| `--group-id <id>` | Filter by policy group | `--group-id <gid>` |
|
|
24
24
|
| `--version-id <id>` | Filter by version | `--version-id <vid>` |
|
|
25
25
|
| `--status <status>` | Filter by status | `--status SUCCESS` |
|
|
26
|
-
| `--start-date <date>` |
|
|
27
|
-
| `--end-date <date>` |
|
|
26
|
+
| `--start-date <date>` | First day, inclusive | `--start-date 2025-01-01` |
|
|
27
|
+
| `--end-date <date>` | Last day, inclusive | `--end-date 2025-01-31` |
|
|
28
|
+
|
|
29
|
+
Dates are `yyyy-MM-dd`, whole days in the organization time zone (`tenantTimezone` in
|
|
30
|
+
`lexq auth whoami`), not UTC.
|
|
28
31
|
|
|
29
32
|
### Execution Statuses
|
|
30
33
|
|
|
@@ -53,6 +56,10 @@ Returns full detail including `inputFacts`, `executionTraces`, and `decisionTrac
|
|
|
53
56
|
lexq history stats
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
Without dates the window is the last 30 days through today, in the organization time zone. At most
|
|
60
|
+
31 days per call; a longer range is rejected with `AN-039`. `--start-date` alone runs through
|
|
61
|
+
today.
|
|
62
|
+
|
|
56
63
|
Returns KPI summary:
|
|
57
64
|
|
|
58
65
|
```json
|
|
@@ -98,8 +105,12 @@ Filter options:
|
|
|
98
105
|
| `--task-type <type>` | Task type | `PLATFORM_WEBHOOK`, `SCHEDULED_DEPLOYMENT` |
|
|
99
106
|
| `--status <status>` | Log status | `PENDING`, `RESOLVED`, `IGNORED` |
|
|
100
107
|
| `--keyword <kw>` | Search in refId, refSubId, errorMessage | any string |
|
|
101
|
-
| `--start-date <date>` |
|
|
102
|
-
| `--end-date <date>` |
|
|
108
|
+
| `--start-date <date>` | First day, inclusive | `2025-01-01` |
|
|
109
|
+
| `--end-date <date>` | Last day, inclusive | `2025-01-31` |
|
|
110
|
+
|
|
111
|
+
Without dates the list covers 7 days back through today, in the organization time zone. A failure
|
|
112
|
+
older than that is left out even while it is still `PENDING`. Pass an earlier `--start-date` to
|
|
113
|
+
see it.
|
|
103
114
|
|
|
104
115
|
### Get Failure Log Detail
|
|
105
116
|
|
|
@@ -242,12 +253,12 @@ lexq profile <groupId>
|
|
|
242
253
|
lexq profile <groupId> --rule <ruleId>
|
|
243
254
|
|
|
244
255
|
# Defaults: live version, last 24h, cache HIT
|
|
245
|
-
lexq profile <groupId> --version <vid> --cache MISS \
|
|
256
|
+
lexq profile <groupId> --version-id <vid> --cache MISS \
|
|
246
257
|
--from 2026-07-01T00:00:00Z --to 2026-08-01T00:00:00Z
|
|
247
258
|
```
|
|
248
259
|
|
|
249
260
|
`--from` / `--to` are ISO-8601 **instants** here (`2026-07-01T00:00:00Z`), unlike `replay start`
|
|
250
|
-
which takes `yyyy-MM-dd`
|
|
261
|
+
which takes `yyyy-MM-dd` days in the organization time zone.
|
|
251
262
|
|
|
252
263
|
`p50` / `p95` / `p99` are `null` when the sample is under 100 — the engine reports insufficient
|
|
253
264
|
data rather than inventing a number. `n` is always present.
|
|
@@ -264,7 +275,7 @@ lexq history list --status ERROR --page 0 --size 10
|
|
|
264
275
|
# 3. For a specific bad decision, get the deterministic why
|
|
265
276
|
lexq provenance get --trace-id <traceId>
|
|
266
277
|
|
|
267
|
-
# 4. Check for system failures
|
|
278
|
+
# 4. Check for system failures (covers 7 days back by default; add --start-date for older ones)
|
|
268
279
|
lexq logs list --status PENDING --page 0 --size 10
|
|
269
280
|
|
|
270
281
|
# 5. Inspect and mark
|
|
@@ -390,6 +390,10 @@ lexq logs bulk-action --ids "id4,id5" --action IGNORE
|
|
|
390
390
|
lexq logs list --status PENDING --page 0 --size 10
|
|
391
391
|
```
|
|
392
392
|
|
|
393
|
+
Without `--start-date`, `logs list` covers 7 days back through today, in the organization time
|
|
394
|
+
zone. An empty list in step 5 means there is no PENDING failure in that window, not that there is
|
|
395
|
+
none at all. Add an earlier `--start-date` to steps 1 and 5 to see older ones.
|
|
396
|
+
|
|
393
397
|
`RESOLVE` and `IGNORE` differ only in intent — both remove the log from PENDING. Use `RESOLVE`
|
|
394
398
|
when the underlying problem was fixed, `IGNORE` when it does not need fixing.
|
|
395
399
|
|
|
@@ -153,7 +153,12 @@ Prefixes you will encounter through the CLI:
|
|
|
153
153
|
1. **Fact keys start with a letter,** then letters, numbers, and underscores. Casing is yours to
|
|
154
154
|
choose and keys are case-sensitive. These docs use `camelCase`: `paymentAmount`, `customerTier`.
|
|
155
155
|
2. **IDs are UUIDs.** Always copy the full ID from list/create output — do not guess.
|
|
156
|
-
3. **Dates
|
|
156
|
+
3. **Dates come in two shapes.** A date filter (`--start-date`, `--end-date`, `--from` / `--to` on
|
|
157
|
+
`replay start` and `simulation list`) takes `yyyy-MM-dd` and means a whole calendar day in the
|
|
158
|
+
organization time zone, not UTC. `lexq auth whoami` shows that zone as `tenantTimezone`. An
|
|
159
|
+
instant (`--effective-from`, `--effective-to`, `--from` / `--to` on `profile`) takes ISO 8601
|
|
160
|
+
with `Z` or an offset: `2026-10-01T00:00:00Z` or `2026-10-01T09:00:00+09:00`. Tables print
|
|
161
|
+
times in UTC with a trailing `Z`.
|
|
157
162
|
4. **JSON bodies via `--json`.** Most create/update commands accept `--json '<body>'` for the request body.
|
|
158
163
|
5. **File input via `--file`.** `analytics dry-run`, `dry-run-compare`, `simulation start` accept
|
|
159
164
|
`--file path/to/body.json` as an alternative to `--json`. `analytics dataset upload` requires
|
|
@@ -251,6 +251,9 @@ lexq analytics simulation start --json '{
|
|
|
251
251
|
| `MANUAL` | `REQUEST_BODY` | Provide `manualData` array in the request |
|
|
252
252
|
| `UPLOADED` | `S3_BUCKET` | Reference an uploaded dataset by `path` |
|
|
253
253
|
|
|
254
|
+
`HISTORICAL` takes `from` / `to` as `yyyy-MM-dd`, whole days in the organization time zone, both
|
|
255
|
+
inclusive.
|
|
256
|
+
|
|
254
257
|
### File Upload Dataset
|
|
255
258
|
|
|
256
259
|
```bash
|