@lexq/cli 0.1.38 → 0.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1358,13 +1358,16 @@ function registerDeployCommands(program) {
1358
1358
  "after",
1359
1359
  dedent7`
1360
1360
 
1361
- Lifecycle: Publish (DRAFT→ACTIVE) → Deploy (ACTIVE→LIVE) → Rollback / Undeploy
1361
+ Lifecycle: Publish (DRAFT→ACTIVE) → Deploy or Schedule (ACTIVE→LIVE) → Rollback / Undeploy
1362
1362
 
1363
1363
  Commands:
1364
1364
  publish Lock a DRAFT version (DRAFT → ACTIVE)
1365
1365
  live Push an ACTIVE version to production traffic
1366
1366
  rollback Revert to the previous deployed version
1367
1367
  undeploy Remove the live version (stops all traffic)
1368
+ schedule Schedule an ACTIVE version to auto-deploy at its effective start
1369
+ unschedule Cancel the pending scheduled deployment
1370
+ schedules List scheduled deployments (all statuses)
1368
1371
  history List deployment history with filters
1369
1372
  detail Get deployment detail with integrity check
1370
1373
  overview Show all groups' deployment status at a glance
@@ -1502,10 +1505,7 @@ function registerDeployCommands(program) {
1502
1505
  process.exit(1);
1503
1506
  }
1504
1507
  });
1505
- deploy.command("history").description("List deployment history").option("--group-id <groupId>", "Filter by policy group").option(
1506
- "--types <types>",
1507
- "Filter by types (comma-separated: PUBLISH,DEPLOY,ROLLBACK,UNDEPLOY)"
1508
- ).option("--start-date <date>", "Start date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
1508
+ 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 date (yyyy-MM-dd)").option("--end-date <date>", "End date (yyyy-MM-dd)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
1509
1509
  "after",
1510
1510
  dedent7`
1511
1511
 
@@ -1660,6 +1660,119 @@ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1660
1660
  process.exit(1);
1661
1661
  }
1662
1662
  });
1663
+ deploy.command("schedule").description("Schedule an ACTIVE version to auto-deploy at its effective start").requiredOption("--group-id <groupId>", "Policy group ID").requiredOption("--version-id <versionId>", "Version ID to schedule").requiredOption("--memo <memo>", "Schedule memo").addHelpText(
1664
+ "after",
1665
+ dedent7`
1666
+
1667
+ The version must be ACTIVE with a future effective start date; the system
1668
+ deploys it automatically at that time (within one scheduler tick, ≤60s).
1669
+ One pending schedule per group. Manual deploy/rollback/undeploy, starting
1670
+ an A/B test, or archiving the group cancels the pending schedule.
1671
+
1672
+ Example:
1673
+ $ lexq deploy schedule --group-id <gid> --version-id <vid> --memo "Q4 pricing"
1674
+ `
1675
+ ).action(async (opts) => {
1676
+ try {
1677
+ const globalOpts = program.opts();
1678
+ const data = await apiRequest(
1679
+ "POST",
1680
+ `policy-groups/${opts.groupId}/schedule`,
1681
+ {
1682
+ apiKey: globalOpts.apiKey,
1683
+ baseUrl: globalOpts.baseUrl,
1684
+ dryRun: globalOpts.dryRun,
1685
+ verbose: globalOpts.verbose,
1686
+ body: { versionId: opts.versionId, memo: opts.memo }
1687
+ }
1688
+ );
1689
+ console.log(`\u2713 Scheduled v${data.versionNo ?? "?"} for ${data.scheduledFor}.`);
1690
+ } catch (error) {
1691
+ printError(error);
1692
+ process.exit(1);
1693
+ }
1694
+ });
1695
+ deploy.command("unschedule").description("Cancel the pending scheduled deployment for a group").requiredOption("--group-id <groupId>", "Policy group ID").option("--force", "Skip confirmation prompt").addHelpText(
1696
+ "after",
1697
+ dedent7`
1698
+
1699
+ Cancels the PENDING schedule only — the version itself is not affected.
1700
+
1701
+ Example:
1702
+ $ lexq deploy unschedule --group-id <gid> --force
1703
+ `
1704
+ ).action(async (opts) => {
1705
+ try {
1706
+ const globalOpts = program.opts();
1707
+ if (!opts.force) {
1708
+ const { createInterface: createInterface2 } = await import("readline/promises");
1709
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
1710
+ const answer = await rl.question(
1711
+ `Cancel the pending scheduled deployment for group ${opts.groupId}? [y/N] `
1712
+ );
1713
+ rl.close();
1714
+ if (answer.toLowerCase() !== "y") {
1715
+ console.log("Canceled.");
1716
+ return;
1717
+ }
1718
+ }
1719
+ await apiRequest("DELETE", `policy-groups/${opts.groupId}/schedule`, {
1720
+ apiKey: globalOpts.apiKey,
1721
+ baseUrl: globalOpts.baseUrl,
1722
+ dryRun: globalOpts.dryRun,
1723
+ verbose: globalOpts.verbose
1724
+ });
1725
+ console.log(`\u2713 Scheduled deployment canceled for group ${opts.groupId}.`);
1726
+ } catch (error) {
1727
+ printError(error);
1728
+ process.exit(1);
1729
+ }
1730
+ });
1731
+ deploy.command("schedules").description("List scheduled deployments (all statuses, newest first)").option("--page <number>", "Page number", "0").option("--size <number>", "Page size", "20").addHelpText(
1732
+ "after",
1733
+ dedent7`
1734
+
1735
+ Example:
1736
+ $ lexq deploy schedules --format table
1737
+ `
1738
+ ).action(async (opts) => {
1739
+ try {
1740
+ const globalOpts = program.opts();
1741
+ const format = globalOpts.format ?? "json";
1742
+ const data = await apiRequest(
1743
+ "GET",
1744
+ "policy-groups/schedules",
1745
+ {
1746
+ apiKey: globalOpts.apiKey,
1747
+ baseUrl: globalOpts.baseUrl,
1748
+ dryRun: globalOpts.dryRun,
1749
+ verbose: globalOpts.verbose,
1750
+ params: { page: opts.page, size: opts.size }
1751
+ }
1752
+ );
1753
+ if (format === "table") {
1754
+ printTable(
1755
+ ["Status", "Group", "Version", "Scheduled For", "By", "Result"],
1756
+ data.content.map((s) => [
1757
+ s.status,
1758
+ s.policyGroupName ?? s.policyGroupId.substring(0, 8),
1759
+ s.versionNo != null ? `v${s.versionNo}` : "\u2013",
1760
+ s.scheduledFor.substring(0, 16),
1761
+ s.scheduledByName,
1762
+ s.status === "EXECUTED" ? s.executedAt?.substring(0, 16) ?? "\u2013" : s.status === "CANCELED" ? s.canceledReason ?? "\u2013" : s.status === "FAILED" ? s.failedReason ?? "\u2013" : "\u2013"
1763
+ ]),
1764
+ { truncate: 20 }
1765
+ );
1766
+ console.log(`
1767
+ ${data.totalElements} total \xB7 page ${data.pageNo + 1}/${data.totalPages}`);
1768
+ } else {
1769
+ printJson(data);
1770
+ }
1771
+ } catch (error) {
1772
+ printError(error);
1773
+ process.exit(1);
1774
+ }
1775
+ });
1663
1776
  }
1664
1777
  async function warnUnregisteredFacts(globalOpts, groupId, versionId) {
1665
1778
  if (globalOpts.dryRun) return;
@@ -2206,13 +2319,16 @@ var TaskType = [
2206
2319
  // Internal
2207
2320
  "IMAGE_PROCESSING",
2208
2321
  "DAILY_SETTLEMENT",
2209
- "PLATFORM_WEBHOOK"
2322
+ "PLATFORM_WEBHOOK",
2323
+ "SCHEDULED_DEPLOYMENT"
2210
2324
  ];
2211
2325
  var PlatformEventType = [
2212
2326
  "VERSION_PUBLISHED",
2213
2327
  "DEPLOYED",
2214
2328
  "ROLLED_BACK",
2215
- "UNDEPLOYED"
2329
+ "UNDEPLOYED",
2330
+ "DEPLOY_SCHEDULED",
2331
+ "DEPLOY_SCHEDULE_CANCELED"
2216
2332
  ];
2217
2333
  var WebhookPayloadFormat = ["GENERIC", "SLACK"];
2218
2334
 
@@ -3817,7 +3933,7 @@ function registerDeployTools(server, callApi) {
3817
3933
  "lexq_deploy_live",
3818
3934
  {
3819
3935
  title: "Deploy to Live",
3820
- description: "Deploy an ACTIVE (published) version to live traffic. Takes effect immediately. Undefined facts do not block deployment (INV-4); use lexq_facts_unregistered to review what the version references but has not defined.",
3936
+ 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 (INV-4); use lexq_facts_unregistered to review what the version references but has not defined.",
3821
3937
  inputSchema: {
3822
3938
  groupId: z5.string().uuid().describe("Policy group ID"),
3823
3939
  versionId: z5.string().uuid().describe("Version ID to deploy"),
@@ -3856,6 +3972,44 @@ function registerDeployTools(server, callApi) {
3856
3972
  body: { memo }
3857
3973
  })
3858
3974
  );
3975
+ server.registerTool(
3976
+ "lexq_deploy_schedule",
3977
+ {
3978
+ title: "Schedule Deployment",
3979
+ 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).",
3980
+ inputSchema: {
3981
+ groupId: z5.string().uuid().describe("Policy group ID"),
3982
+ versionId: z5.string().uuid().describe("ACTIVE version ID with a future effective start date"),
3983
+ memo: z5.string().min(1).describe("Schedule memo (required)")
3984
+ }
3985
+ },
3986
+ async ({ groupId, versionId, memo }) => callApi("POST", `policy-groups/${groupId}/schedule`, {
3987
+ body: { versionId, memo }
3988
+ })
3989
+ );
3990
+ server.registerTool(
3991
+ "lexq_deploy_unschedule",
3992
+ {
3993
+ title: "Cancel Scheduled Deployment",
3994
+ description: "Cancel the pending scheduled deployment for a group. The version itself is not affected. Fails with P-039 if no pending schedule exists.",
3995
+ inputSchema: {
3996
+ groupId: z5.string().uuid().describe("Policy group ID")
3997
+ }
3998
+ },
3999
+ async ({ groupId }) => callApi("DELETE", `policy-groups/${groupId}/schedule`)
4000
+ );
4001
+ server.registerTool(
4002
+ "lexq_deploy_schedules",
4003
+ {
4004
+ title: "List Scheduled Deployments",
4005
+ description: "List scheduled deployments across all groups (all statuses: PENDING, EXECUTED, CANCELED, FAILED), newest first.",
4006
+ inputSchema: {
4007
+ page: z5.number().int().min(0).default(0).describe("Page number"),
4008
+ size: z5.number().int().min(1).max(100).default(20).describe("Page size")
4009
+ }
4010
+ },
4011
+ async ({ page, size }) => callApi("GET", "policy-groups/schedules", { params: paginationParams(page, size) })
4012
+ );
3859
4013
  server.registerTool(
3860
4014
  "lexq_deploy_history",
3861
4015
  {
@@ -3865,9 +4019,7 @@ function registerDeployTools(server, callApi) {
3865
4019
  page: z5.number().int().min(0).default(0).describe("Page number"),
3866
4020
  size: z5.number().int().min(1).max(100).default(20).describe("Page size"),
3867
4021
  groupId: z5.string().uuid().optional().describe("Filter by group ID"),
3868
- types: z5.string().optional().describe(
3869
- "Filter by deployment types (comma-separated: PUBLISH,DEPLOY,ROLLBACK,UNDEPLOY)"
3870
- ),
4022
+ types: z5.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
3871
4023
  startDate: z5.string().optional().describe("Start date (yyyy-MM-dd)"),
3872
4024
  endDate: z5.string().optional().describe("End date (yyyy-MM-dd)")
3873
4025
  }
@@ -35,6 +35,12 @@ declare function paginationParams(page?: number, size?: number): Record<string,
35
35
  */
36
36
  declare function formatUnregisteredFactWarning(meta: unknown): string | null;
37
37
 
38
+ declare class ApiError extends Error {
39
+ readonly statusCode: number;
40
+ readonly errorCode: string | null;
41
+ constructor(statusCode: number, errorCode: string | null, message: string);
42
+ }
43
+
38
44
  /**
39
45
  * Registers all MCP tools on the given server.
40
46
  *
@@ -43,4 +49,4 @@ declare function formatUnregisteredFactWarning(meta: unknown): string | null;
43
49
  */
44
50
  declare function registerAllTools(server: McpServer, callApi: CallApi): void;
45
51
 
46
- export { type CallApi, type McpToolResult, formatUnregisteredFactWarning, paginationParams, registerAllTools };
52
+ export { ApiError, type CallApi, type McpToolResult, formatUnregisteredFactWarning, paginationParams, registerAllTools };
@@ -154,6 +154,18 @@ import { join } from "path";
154
154
  var CONFIG_DIR = join(homedir(), ".lexq");
155
155
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
156
156
 
157
+ // src/lib/api-client.ts
158
+ var ApiError = class extends Error {
159
+ constructor(statusCode, errorCode, message) {
160
+ super(message);
161
+ this.statusCode = statusCode;
162
+ this.errorCode = errorCode;
163
+ this.name = "ApiError";
164
+ }
165
+ statusCode;
166
+ errorCode;
167
+ };
168
+
157
169
  // src/mcp/tools/_shared.ts
158
170
  function paginationParams(page, size) {
159
171
  const params = {};
@@ -532,7 +544,7 @@ function registerDeployTools(server, callApi) {
532
544
  "lexq_deploy_live",
533
545
  {
534
546
  title: "Deploy to Live",
535
- description: "Deploy an ACTIVE (published) version to live traffic. Takes effect immediately. Undefined facts do not block deployment (INV-4); use lexq_facts_unregistered to review what the version references but has not defined.",
547
+ 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 (INV-4); use lexq_facts_unregistered to review what the version references but has not defined.",
536
548
  inputSchema: {
537
549
  groupId: z5.string().uuid().describe("Policy group ID"),
538
550
  versionId: z5.string().uuid().describe("Version ID to deploy"),
@@ -571,6 +583,44 @@ function registerDeployTools(server, callApi) {
571
583
  body: { memo }
572
584
  })
573
585
  );
586
+ server.registerTool(
587
+ "lexq_deploy_schedule",
588
+ {
589
+ title: "Schedule Deployment",
590
+ 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).",
591
+ inputSchema: {
592
+ groupId: z5.string().uuid().describe("Policy group ID"),
593
+ versionId: z5.string().uuid().describe("ACTIVE version ID with a future effective start date"),
594
+ memo: z5.string().min(1).describe("Schedule memo (required)")
595
+ }
596
+ },
597
+ async ({ groupId, versionId, memo }) => callApi("POST", `policy-groups/${groupId}/schedule`, {
598
+ body: { versionId, memo }
599
+ })
600
+ );
601
+ server.registerTool(
602
+ "lexq_deploy_unschedule",
603
+ {
604
+ title: "Cancel Scheduled Deployment",
605
+ description: "Cancel the pending scheduled deployment for a group. The version itself is not affected. Fails with P-039 if no pending schedule exists.",
606
+ inputSchema: {
607
+ groupId: z5.string().uuid().describe("Policy group ID")
608
+ }
609
+ },
610
+ async ({ groupId }) => callApi("DELETE", `policy-groups/${groupId}/schedule`)
611
+ );
612
+ server.registerTool(
613
+ "lexq_deploy_schedules",
614
+ {
615
+ title: "List Scheduled Deployments",
616
+ description: "List scheduled deployments across all groups (all statuses: PENDING, EXECUTED, CANCELED, FAILED), newest first.",
617
+ inputSchema: {
618
+ page: z5.number().int().min(0).default(0).describe("Page number"),
619
+ size: z5.number().int().min(1).max(100).default(20).describe("Page size")
620
+ }
621
+ },
622
+ async ({ page, size }) => callApi("GET", "policy-groups/schedules", { params: paginationParams(page, size) })
623
+ );
574
624
  server.registerTool(
575
625
  "lexq_deploy_history",
576
626
  {
@@ -580,9 +630,7 @@ function registerDeployTools(server, callApi) {
580
630
  page: z5.number().int().min(0).default(0).describe("Page number"),
581
631
  size: z5.number().int().min(1).max(100).default(20).describe("Page size"),
582
632
  groupId: z5.string().uuid().optional().describe("Filter by group ID"),
583
- types: z5.string().optional().describe(
584
- "Filter by deployment types (comma-separated: PUBLISH,DEPLOY,ROLLBACK,UNDEPLOY)"
585
- ),
633
+ types: z5.string().optional().describe("Filter by deployment types (comma-separated: DEPLOY,ROLLBACK,UNDEPLOY)"),
586
634
  startDate: z5.string().optional().describe("Start date (yyyy-MM-dd)"),
587
635
  endDate: z5.string().optional().describe("End date (yyyy-MM-dd)")
588
636
  }
@@ -1182,13 +1230,16 @@ var TaskType = [
1182
1230
  // Internal
1183
1231
  "IMAGE_PROCESSING",
1184
1232
  "DAILY_SETTLEMENT",
1185
- "PLATFORM_WEBHOOK"
1233
+ "PLATFORM_WEBHOOK",
1234
+ "SCHEDULED_DEPLOYMENT"
1186
1235
  ];
1187
1236
  var PlatformEventType = [
1188
1237
  "VERSION_PUBLISHED",
1189
1238
  "DEPLOYED",
1190
1239
  "ROLLED_BACK",
1191
- "UNDEPLOYED"
1240
+ "UNDEPLOYED",
1241
+ "DEPLOY_SCHEDULED",
1242
+ "DEPLOY_SCHEDULE_CANCELED"
1192
1243
  ];
1193
1244
  var WebhookPayloadFormat = ["GENERIC", "SLACK"];
1194
1245
 
@@ -1396,6 +1447,7 @@ function registerAllTools(server, callApi) {
1396
1447
  registerWebhookSubscriptionTools(server, callApi);
1397
1448
  }
1398
1449
  export {
1450
+ ApiError,
1399
1451
  formatUnregisteredFactWarning,
1400
1452
  paginationParams,
1401
1453
  registerAllTools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexq/cli",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
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": {
@@ -29,7 +29,10 @@
29
29
  "lint": "eslint src/",
30
30
  "typecheck": "tsc --noEmit",
31
31
  "start": "node dist/index.js",
32
- "prepublishOnly": "pnpm build"
32
+ "prepublishOnly": "pnpm build",
33
+ "knip": "knip",
34
+ "format": "prettier --write \"src/**/*.ts\"",
35
+ "format:check": "prettier --check \"src/**/*.ts\""
33
36
  },
34
37
  "keywords": [
35
38
  "lexq",
@@ -57,15 +60,14 @@
57
60
  "cli-table3": "^0.6.5",
58
61
  "commander": "^13.1.0",
59
62
  "dedent": "^1.7.2",
60
- "ora": "^8.2.0",
61
- "prettier": "^3.8.1",
62
63
  "zod": "^3.25.76"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@eslint/js": "^9.20.0",
66
- "@types/dedent": "^0.7.2",
67
67
  "@types/node": "^22.12.0",
68
68
  "eslint": "^10.2.0",
69
+ "knip": "^6.29.0",
70
+ "prettier": "^3.9.6",
69
71
  "tsup": "^8.5.1",
70
72
  "typescript": "^5.7.0",
71
73
  "typescript-eslint": "^8.24.0"