@01.software/cli 0.9.0 → 0.10.1

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
@@ -128,7 +128,7 @@ ${entry}
128
128
  // src/lib/output.ts
129
129
  import pc from "picocolors";
130
130
 
131
- // src/lib/api-error.ts
131
+ // src/lib/admin-error.ts
132
132
  var PERMISSION_CODES = [
133
133
  "tenant_mismatch",
134
134
  "account_suspended",
@@ -515,6 +515,40 @@ function failArg(label, value, expectedKind) {
515
515
  detail: { message, value, field: label }
516
516
  });
517
517
  }
518
+ function stringifyValue(value) {
519
+ try {
520
+ return JSON.stringify(value);
521
+ } catch {
522
+ return String(value);
523
+ }
524
+ }
525
+ function formatSchemaIssues(label, issues) {
526
+ return issues.map((issue) => {
527
+ const path = issue.path.length > 0 ? issue.path.join(".") : label;
528
+ return `${path}: ${issue.message}`;
529
+ }).join("; ");
530
+ }
531
+ function parseWithSchema(value, label, schema) {
532
+ const parsed = schema.safeParse(value);
533
+ if (parsed.success) return parsed.data;
534
+ const issues = parsed.error.issues.map((issue) => ({
535
+ path: issue.path.map(String),
536
+ message: issue.message
537
+ }));
538
+ const message = `Invalid value for --${label}: ${formatSchemaIssues(label, issues)}`;
539
+ exitWithError({
540
+ type: "validation",
541
+ code: "invalid_argument",
542
+ field: label,
543
+ message,
544
+ detail: {
545
+ message,
546
+ value: stringifyValue(value),
547
+ field: label,
548
+ issues
549
+ }
550
+ });
551
+ }
518
552
  function parseJson(value, label, options = {}) {
519
553
  let parsed;
520
554
  try {
@@ -736,15 +770,65 @@ function registerCrudCommands(program2, getClient2, getFormat2) {
736
770
  }
737
771
 
738
772
  // src/commands/order.ts
773
+ import { z } from "zod";
774
+ var idSchema = z.union([z.string().min(1), z.number()]).transform(String);
775
+ var customerSnapshotSchema = z.object({
776
+ email: z.string().email("Invalid email format"),
777
+ name: z.string().optional(),
778
+ phone: z.string().optional()
779
+ }).strict();
780
+ var shippingAddressSchema = z.object({
781
+ postalCode: z.string().optional(),
782
+ address: z.string().optional(),
783
+ detailAddress: z.string().optional(),
784
+ deliveryMessage: z.string().optional(),
785
+ recipientName: z.string().optional(),
786
+ phone: z.string().optional()
787
+ }).strict();
788
+ var orderItemSchema = z.object({
789
+ product: idSchema,
790
+ variant: idSchema,
791
+ option: idSchema,
792
+ quantity: z.number().int().positive("quantity must be a positive integer"),
793
+ unitPrice: z.number().optional(),
794
+ totalPrice: z.number().optional()
795
+ }).strict();
796
+ var orderItemsSchema = z.array(orderItemSchema).min(1, "At least one order item is required").max(100, "Maximum 100 items per order");
797
+ var orderStatusSchema = z.enum([
798
+ "pending",
799
+ "paid",
800
+ "failed",
801
+ "canceled",
802
+ "preparing",
803
+ "shipped",
804
+ "delivered",
805
+ "confirmed"
806
+ ]);
807
+ var fulfillmentItemsSchema = z.array(
808
+ z.object({
809
+ orderItem: idSchema,
810
+ quantity: z.number().int().positive("quantity must be a positive integer")
811
+ }).strict()
812
+ ).min(1, "At least one fulfillment item is required").max(100, "Maximum 100 items per fulfillment");
739
813
  function registerOrderCommands(program2, getClient2, getFormat2) {
740
814
  const order = program2.command("order").description("Order management");
741
815
  order.command("create").description("Create a new order").option("--payment-id <id>", "Payment ID").requiredOption("--order-number <num>", "Order number").requiredOption("--email <email>", "Customer email").option("--customer <id>", "Customer ID").option("--name <name>", "Customer name").option("--phone <phone>", "Customer phone").requiredOption("--shipping-address <json>", "Shipping address (JSON)").requiredOption("--products <json>", "Order products array (JSON)").requiredOption("--total-amount <n>", "Total amount", parseFloat).option("--dry-run", "Validate inputs without executing").action(async (opts) => {
742
816
  try {
743
- const shippingAddress = parseJson(
744
- opts.shippingAddress,
745
- "shipping-address"
817
+ const shippingAddress = parseWithSchema(
818
+ parseJson(opts.shippingAddress, "shipping-address"),
819
+ "shipping-address",
820
+ shippingAddressSchema
821
+ );
822
+ const orderItems = parseWithSchema(
823
+ parseJsonArray(opts.products, "products"),
824
+ "products",
825
+ orderItemsSchema
826
+ );
827
+ const totalAmount = parseWithSchema(
828
+ opts.totalAmount,
829
+ "total-amount",
830
+ z.number().nonnegative("totalAmount must be non-negative")
746
831
  );
747
- const orderItems = parseJsonArray(opts.products, "products");
748
832
  const data = {
749
833
  pgPaymentId: opts.paymentId,
750
834
  orderNumber: opts.orderNumber,
@@ -756,7 +840,7 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
756
840
  customer: opts.customer,
757
841
  shippingAddress,
758
842
  orderItems,
759
- totalAmount: opts.totalAmount
843
+ totalAmount
760
844
  };
761
845
  if (opts.dryRun) {
762
846
  printResult(
@@ -787,7 +871,8 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
787
871
  });
788
872
  order.command("update <orderNumber>").description("Update order status").requiredOption("--status <status>", "New status").option("--dry-run", "Validate inputs without executing").action(async (orderNumber, opts) => {
789
873
  try {
790
- const data = { orderNumber, status: opts.status };
874
+ const status = parseWithSchema(opts.status, "status", orderStatusSchema);
875
+ const data = { orderNumber, status };
791
876
  if (opts.dryRun) {
792
877
  printResult(
793
878
  { dryRun: true, valid: true, action: "order update", data },
@@ -804,7 +889,11 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
804
889
  });
805
890
  order.command("checkout").description("Convert a cart to an order").requiredOption("--cart-id <id>", "Cart ID").option("--payment-id <id>", "Payment ID (optional for free orders)").requiredOption("--order-number <num>", "Order number").requiredOption("--customer <json>", "Customer snapshot (JSON)").option("--dry-run", "Validate inputs without executing").action(async (opts) => {
806
891
  try {
807
- const customerSnapshot = parseJson(opts.customer, "customer");
892
+ const customerSnapshot = parseWithSchema(
893
+ parseJson(opts.customer, "customer"),
894
+ "customer",
895
+ customerSnapshotSchema
896
+ );
808
897
  const data = {
809
898
  cartId: opts.cartId,
810
899
  pgPaymentId: opts.paymentId,
@@ -830,7 +919,11 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
830
919
  });
831
920
  order.command("fulfill <orderNumber>").description("Create a fulfillment for an order").requiredOption("--items <json>", "Fulfillment items array (JSON)").option("--carrier <name>", "Shipping carrier").option("--tracking-number <num>", "Tracking number").option("--dry-run", "Validate inputs without executing").action(async (orderNumber, opts) => {
832
921
  try {
833
- const items = parseJsonArray(opts.items, "items");
922
+ const items = parseWithSchema(
923
+ parseJsonArray(opts.items, "items"),
924
+ "items",
925
+ fulfillmentItemsSchema
926
+ );
834
927
  const data = {
835
928
  orderNumber,
836
929
  items,
@@ -857,6 +950,23 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
857
950
  }
858
951
 
859
952
  // src/commands/return.ts
953
+ import { z as z2 } from "zod";
954
+ var idSchema2 = z2.union([z2.string().min(1), z2.number()]).transform(String);
955
+ var returnReasonSchema = z2.enum(["change_of_mind", "defective", "wrong_delivery", "damaged", "other"]).optional();
956
+ var returnItemsSchema = z2.array(
957
+ z2.object({
958
+ orderItem: idSchema2,
959
+ quantity: z2.number().int().positive("quantity must be a positive integer"),
960
+ restockAction: z2.enum(["return_to_stock", "discard"]).default("return_to_stock")
961
+ }).strict()
962
+ ).min(1, "At least one return item is required").max(100, "Too many return items");
963
+ var returnStatusSchema = z2.enum([
964
+ "processing",
965
+ "approved",
966
+ "rejected",
967
+ "completed"
968
+ ]);
969
+ var refundAmountSchema = z2.number().nonnegative("refundAmount must be non-negative");
860
970
  function registerReturnCommands(program2, getClient2, getFormat2) {
861
971
  const ret = program2.command("return").description("Return management");
862
972
  ret.command("create <orderNumber>").description("Create a return request").requiredOption("--products <json>", "Return products array (JSON)").requiredOption("--refund-amount <n>", "Refund amount", parseFloat).option(
@@ -864,12 +974,26 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
864
974
  "Return reason (change_of_mind, defective, wrong_delivery, damaged, other)"
865
975
  ).option("--reason-detail <text>", "Detailed reason").option("--dry-run", "Validate inputs without executing").action(async (orderNumber, opts) => {
866
976
  try {
867
- const returnItems = parseJsonArray(opts.products, "products");
977
+ const returnItems = parseWithSchema(
978
+ parseJsonArray(opts.products, "products"),
979
+ "products",
980
+ returnItemsSchema
981
+ );
982
+ const refundAmount = parseWithSchema(
983
+ opts.refundAmount,
984
+ "refund-amount",
985
+ refundAmountSchema
986
+ );
987
+ const reason = parseWithSchema(
988
+ opts.reason,
989
+ "reason",
990
+ returnReasonSchema
991
+ );
868
992
  const data = {
869
993
  orderNumber,
870
994
  returnItems,
871
- refundAmount: opts.refundAmount,
872
- reason: opts.reason,
995
+ refundAmount,
996
+ reason,
873
997
  reasonDetail: opts.reasonDetail
874
998
  };
875
999
  if (opts.dryRun) {
@@ -894,7 +1018,8 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
894
1018
  "New status (processing, approved, rejected, completed)"
895
1019
  ).option("--dry-run", "Validate inputs without executing").action(async (returnId, opts) => {
896
1020
  try {
897
- const data = { returnId, status: opts.status };
1021
+ const status = parseWithSchema(opts.status, "status", returnStatusSchema);
1022
+ const data = { returnId, status };
898
1023
  if (opts.dryRun) {
899
1024
  printResult(
900
1025
  { dryRun: true, valid: true, action: "return update", data },
@@ -911,13 +1036,27 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
911
1036
  });
912
1037
  ret.command("refund <orderNumber>").description("Return with refund").requiredOption("--products <json>", "Return products array (JSON)").requiredOption("--refund-amount <n>", "Refund amount", parseFloat).requiredOption("--payment-id <id>", "Payment ID").option("--reason <reason>", "Return reason").option("--reason-detail <text>", "Detailed reason").option("--refund-receipt-url <url>", "Refund receipt URL").option("--dry-run", "Validate inputs without executing").action(async (orderNumber, opts) => {
913
1038
  try {
914
- const returnItems = parseJsonArray(opts.products, "products");
1039
+ const returnItems = parseWithSchema(
1040
+ parseJsonArray(opts.products, "products"),
1041
+ "products",
1042
+ returnItemsSchema
1043
+ );
1044
+ const refundAmount = parseWithSchema(
1045
+ opts.refundAmount,
1046
+ "refund-amount",
1047
+ refundAmountSchema
1048
+ );
1049
+ const reason = parseWithSchema(
1050
+ opts.reason,
1051
+ "reason",
1052
+ returnReasonSchema
1053
+ );
915
1054
  const data = {
916
1055
  orderNumber,
917
1056
  returnItems,
918
- refundAmount: opts.refundAmount,
1057
+ refundAmount,
919
1058
  pgPaymentId: opts.paymentId,
920
- reason: opts.reason,
1059
+ reason,
921
1060
  reasonDetail: opts.reasonDetail,
922
1061
  refundReceiptUrl: opts.refundReceiptUrl
923
1062
  };
@@ -1413,25 +1552,265 @@ function registerSchemaCommands(program2, getClient2, getFormat2) {
1413
1552
  });
1414
1553
  }
1415
1554
 
1555
+ // ../contracts/src/tenant/index.ts
1556
+ import { z as z3 } from "zod";
1557
+ var tenantFieldConfigStateSchema = z3.object({
1558
+ hiddenFields: z3.array(z3.string()),
1559
+ isHidden: z3.boolean()
1560
+ }).strict();
1561
+ var tenantContextQuerySchema = z3.object({
1562
+ counts: z3.literal("true").optional()
1563
+ }).strict();
1564
+ var tenantContextToolInputSchema = z3.object({
1565
+ includeCounts: z3.boolean().optional().default(false).describe(
1566
+ "Include per-collection document counts and config status (bypasses cache, slower)"
1567
+ )
1568
+ }).strict();
1569
+ var tenantContextResponseSchema = z3.object({
1570
+ tenant: z3.object({
1571
+ id: z3.string(),
1572
+ name: z3.string(),
1573
+ plan: z3.string(),
1574
+ planSource: z3.string().optional(),
1575
+ authoritative: z3.boolean().optional(),
1576
+ capabilityVersion: z3.string().optional()
1577
+ }).strict(),
1578
+ features: z3.array(z3.string()),
1579
+ collections: z3.object({
1580
+ active: z3.array(z3.string()),
1581
+ inactive: z3.array(z3.string())
1582
+ }).strict(),
1583
+ fieldConfigs: z3.record(z3.string(), tenantFieldConfigStateSchema),
1584
+ counts: z3.record(z3.string(), z3.number()).optional(),
1585
+ config: z3.object({
1586
+ webhookConfigured: z3.boolean()
1587
+ }).strict().optional()
1588
+ }).strict();
1589
+ var tenantFeatureProgressFeatureSchema = z3.enum(["ecommerce"]);
1590
+ var tenantFeatureProgressInputSchema = z3.object({
1591
+ feature: tenantFeatureProgressFeatureSchema.describe(
1592
+ "Feature to inspect for tenant implementation readiness"
1593
+ ),
1594
+ includeEvidence: z3.boolean().optional().default(false).describe("Include sanitized counts and static surface evidence")
1595
+ }).strict();
1596
+ var tenantFeatureProgressStatusSchema = z3.enum([
1597
+ "ready",
1598
+ "attention",
1599
+ "blocked"
1600
+ ]);
1601
+ var tenantFeatureProgressItemStateSchema = z3.enum([
1602
+ "complete",
1603
+ "incomplete",
1604
+ "blocked",
1605
+ "attention",
1606
+ "optional",
1607
+ "unknown",
1608
+ "manual",
1609
+ "not-applicable"
1610
+ ]);
1611
+ var tenantFeatureProgressSeveritySchema = z3.enum([
1612
+ "required",
1613
+ "recommended",
1614
+ "optional"
1615
+ ]);
1616
+ var tenantFeatureProgressEvidenceValueSchema = z3.union([
1617
+ z3.string(),
1618
+ z3.number(),
1619
+ z3.boolean(),
1620
+ z3.null()
1621
+ ]);
1622
+ var tenantFeatureProgressItemSchema = z3.object({
1623
+ id: z3.string(),
1624
+ title: z3.string(),
1625
+ state: tenantFeatureProgressItemStateSchema,
1626
+ severity: tenantFeatureProgressSeveritySchema,
1627
+ summary: z3.string(),
1628
+ evidence: z3.record(z3.string(), tenantFeatureProgressEvidenceValueSchema).optional()
1629
+ }).strict();
1630
+ var tenantFeatureProgressGroupSchema = z3.object({
1631
+ id: z3.string(),
1632
+ title: z3.string(),
1633
+ summary: z3.string().optional(),
1634
+ items: z3.array(tenantFeatureProgressItemSchema)
1635
+ }).strict();
1636
+ var tenantFeatureProgressResponseSchema = z3.object({
1637
+ schemaVersion: z3.literal(1),
1638
+ feature: tenantFeatureProgressFeatureSchema,
1639
+ status: tenantFeatureProgressStatusSchema,
1640
+ generatedAt: z3.string(),
1641
+ tenant: z3.object({
1642
+ id: z3.string(),
1643
+ name: z3.string(),
1644
+ plan: z3.string()
1645
+ }).strict(),
1646
+ capability: z3.object({
1647
+ effectiveFeatures: z3.array(z3.string()),
1648
+ planBlocked: z3.array(z3.string()),
1649
+ closureAdded: z3.array(z3.string())
1650
+ }).strict(),
1651
+ summary: z3.object({
1652
+ complete: z3.number().int().nonnegative(),
1653
+ total: z3.number().int().nonnegative(),
1654
+ blocking: z3.number().int().nonnegative(),
1655
+ manual: z3.number().int().nonnegative(),
1656
+ unknown: z3.number().int().nonnegative()
1657
+ }).strict(),
1658
+ groups: z3.array(tenantFeatureProgressGroupSchema)
1659
+ }).strict();
1660
+ var COLLECTION_SCHEMA_CONTRACT_VERSION = 1;
1661
+ var collectionSchemaEndpointParamsSchema = z3.object({
1662
+ collectionSlug: z3.string().min(1, "collectionSlug is required")
1663
+ }).strict();
1664
+ var collectionFieldOptionSchema = z3.object({
1665
+ label: z3.string(),
1666
+ value: z3.string()
1667
+ }).strict();
1668
+ var collectionFieldSchema = z3.lazy(
1669
+ () => z3.object({
1670
+ name: z3.string(),
1671
+ path: z3.string(),
1672
+ type: z3.string(),
1673
+ required: z3.literal(true).optional(),
1674
+ unique: z3.literal(true).optional(),
1675
+ hasMany: z3.literal(true).optional(),
1676
+ relationTo: z3.union([z3.string(), z3.array(z3.string())]).optional(),
1677
+ options: z3.array(collectionFieldOptionSchema).optional(),
1678
+ hidden: z3.literal(true).optional(),
1679
+ systemManaged: z3.literal(true).optional(),
1680
+ writable: z3.boolean().optional(),
1681
+ fields: z3.array(collectionFieldSchema).optional()
1682
+ }).strict()
1683
+ );
1684
+ var collectionSchemaResponseSchema = z3.object({
1685
+ contractVersion: z3.literal(COLLECTION_SCHEMA_CONTRACT_VERSION),
1686
+ mode: z3.literal("effective"),
1687
+ collection: z3.object({
1688
+ slug: z3.string(),
1689
+ timestamps: z3.boolean(),
1690
+ alwaysActive: z3.boolean(),
1691
+ feature: z3.string().nullable(),
1692
+ systemFields: z3.array(z3.string()),
1693
+ visibility: z3.object({
1694
+ collectionHidden: z3.boolean(),
1695
+ hiddenFields: z3.array(z3.string())
1696
+ }).strict(),
1697
+ fields: z3.array(collectionFieldSchema)
1698
+ }).strict()
1699
+ }).strict();
1700
+
1701
+ // src/commands/feature.ts
1702
+ function getApiUrl2() {
1703
+ return (process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL || "https://api.01.software").replace(/\/$/, "");
1704
+ }
1705
+ function flattenProgress(progress) {
1706
+ return progress.groups.flatMap(
1707
+ (group) => group.items.map((item) => ({
1708
+ group: group.title,
1709
+ item: item.title,
1710
+ state: item.state,
1711
+ severity: item.severity,
1712
+ summary: item.summary
1713
+ }))
1714
+ );
1715
+ }
1716
+ function registerFeatureCommands(program2, getClient2, getFormat2) {
1717
+ const feature = program2.command("feature").description("Feature implementation progress checks");
1718
+ feature.command("check <feature>").description("Check tenant implementation progress for a feature").option("--evidence", "Include sanitized evidence counts and surface flags").action(async (featureName, options) => {
1719
+ try {
1720
+ const input = parseWithSchema(
1721
+ {
1722
+ feature: featureName,
1723
+ includeEvidence: Boolean(options.evidence)
1724
+ },
1725
+ "feature",
1726
+ tenantFeatureProgressInputSchema
1727
+ );
1728
+ const client = getClient2();
1729
+ const baseUrl = getApiUrl2();
1730
+ const search = new URLSearchParams({ feature: input.feature });
1731
+ if (input.includeEvidence) search.set("includeEvidence", "true");
1732
+ const response = await fetch(
1733
+ `${baseUrl}/api/tenants/feature-progress?${search.toString()}`,
1734
+ {
1735
+ headers: {
1736
+ "X-Publishable-Key": client.publishableKey,
1737
+ Authorization: `Bearer ${client.secretKey}`
1738
+ }
1739
+ }
1740
+ );
1741
+ if (!response.ok) {
1742
+ const body = await response.json().catch(() => ({ error: response.statusText }));
1743
+ const err = new Error(
1744
+ body.error || `HTTP ${response.status}`
1745
+ );
1746
+ Object.assign(err, { status: response.status });
1747
+ throw err;
1748
+ }
1749
+ const result = tenantFeatureProgressResponseSchema.parse(
1750
+ await response.json()
1751
+ );
1752
+ const format = getFormat2();
1753
+ printResult(
1754
+ format === "table" ? flattenProgress(result) : result,
1755
+ format
1756
+ );
1757
+ } catch (error) {
1758
+ exitWithError(error);
1759
+ }
1760
+ });
1761
+ }
1762
+
1416
1763
  // src/commands/mcp.ts
1417
1764
  import { resolve, dirname } from "path";
1418
1765
  import { existsSync as existsSync2 } from "fs";
1419
1766
  import { spawn } from "child_process";
1420
1767
  import { fileURLToPath } from "url";
1421
1768
  var __dirname = dirname(fileURLToPath(import.meta.url));
1422
- function findStdioEntry(baseDir = __dirname) {
1423
- const packaged = resolve(baseDir, "mcp/stdio.js");
1424
- if (existsSync2(packaged)) return packaged;
1769
+ function getStdioEntryCandidates(baseDir = __dirname) {
1770
+ const candidates = [
1771
+ {
1772
+ label: "packaged CLI artifact",
1773
+ path: resolve(baseDir, "mcp/stdio.js")
1774
+ }
1775
+ ];
1425
1776
  const roots = ["../../../..", "../../..", "../.."];
1426
- const entries = ["apps/mcp/dist/stdio.js", "apps/mcp/.xmcp/stdio.js"];
1777
+ const entries = [
1778
+ {
1779
+ label: "monorepo build output",
1780
+ path: "apps/mcp/dist/stdio.js"
1781
+ },
1782
+ {
1783
+ label: "xmcp build output",
1784
+ path: "apps/mcp/.xmcp/stdio.js"
1785
+ }
1786
+ ];
1427
1787
  for (const entry of entries) {
1428
1788
  for (const root of roots) {
1429
- const candidate = resolve(baseDir, root, entry);
1430
- if (existsSync2(candidate)) return candidate;
1789
+ candidates.push({
1790
+ label: entry.label,
1791
+ path: resolve(baseDir, root, entry.path)
1792
+ });
1431
1793
  }
1432
1794
  }
1795
+ return candidates;
1796
+ }
1797
+ function findStdioEntry(baseDir = __dirname) {
1798
+ for (const candidate of getStdioEntryCandidates(baseDir)) {
1799
+ if (existsSync2(candidate.path)) return candidate.path;
1800
+ }
1433
1801
  return null;
1434
1802
  }
1803
+ function formatMissingStdioEntryMessage(baseDir = __dirname) {
1804
+ const checked = getStdioEntryCandidates(baseDir).map((candidate) => ` - ${candidate.label}: ${candidate.path}`).join("\n");
1805
+ return [
1806
+ "MCP stdio entry not found.",
1807
+ "Checked:",
1808
+ checked,
1809
+ "Fix:",
1810
+ " - Monorepo checkout: run pnpm --filter mcp build.",
1811
+ " - Published CLI: reinstall or update @01.software/cli so dist/mcp/stdio.js is included."
1812
+ ].join("\n");
1813
+ }
1435
1814
  function createMcpEnv(baseEnv, client) {
1436
1815
  const env = {
1437
1816
  ...baseEnv,
@@ -1442,15 +1821,21 @@ function createMcpEnv(baseEnv, client) {
1442
1821
  return env;
1443
1822
  }
1444
1823
  function registerMcpCommands(program2) {
1445
- program2.command("mcp").description("Start MCP server over stdio").action(() => {
1824
+ program2.command("mcp").description("Start local MCP stdio server for trusted server-key workflows").addHelpText(
1825
+ "after",
1826
+ `
1827
+ Prerequisites:
1828
+ Run 01 login, or set SOFTWARE_PUBLISHABLE_KEY and SOFTWARE_SECRET_KEY.
1829
+ Local stdio exposes the full MCP tool surface; HTTP OAuth MCP uses the
1830
+ narrower remote surface documented in the web integration guide.
1831
+ In a monorepo checkout, build the stdio artifact first:
1832
+ pnpm --filter mcp build
1833
+ `
1834
+ ).action(() => {
1446
1835
  const client = resolveClient(program2.opts().apiKey);
1447
1836
  const stdioEntry = findStdioEntry();
1448
1837
  if (!stdioEntry) {
1449
- exitWithError(
1450
- new Error(
1451
- "MCP server not found. Ensure apps/mcp is built (pnpm --filter mcp build)."
1452
- )
1453
- );
1838
+ exitWithError(new Error(formatMissingStdioEntryMessage()));
1454
1839
  }
1455
1840
  const child = spawn(process.execPath, [stdioEntry], {
1456
1841
  env: createMcpEnv(process.env, client),
@@ -1487,6 +1872,7 @@ registerCartCommands(program, getClient, getFormat);
1487
1872
  registerStockCommands(program, getClient, getFormat);
1488
1873
  registerTransactionCommands(program, getClient, getFormat);
1489
1874
  registerSchemaCommands(program, getClient, getFormat);
1875
+ registerFeatureCommands(program, getClient, getFormat);
1490
1876
  registerMcpCommands(program);
1491
1877
  registerAuthCommands(program);
1492
1878
  program.parse();