@01.software/cli 0.8.0 → 0.10.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/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",
@@ -241,7 +241,7 @@ var CLI_I18N_KO = {
241
241
  PassApiKey: "`--api-key <token>` \uC635\uC158\uC744 \uC804\uB2EC\uD558\uAC70\uB098,",
242
242
  SetEnvVars: "`SOFTWARE_PUBLISHABLE_KEY` / `SOFTWARE_SECRET_KEY` \uD658\uACBD \uBCC0\uC218\uB97C \uC124\uC815\uD558\uC138\uC694.",
243
243
  InvalidApiKeyFormat: "API \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. `sk01_` \uB610\uB294 `pat01_` \uD1A0\uD070\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
244
- LegacyHexCredentialsRejected: "\uB808\uAC70\uC2DC hex \uC790\uACA9 \uC99D\uBA85\uC740 \uB354 \uC774\uC0C1 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 `01 login`\uC73C\uB85C \uB2E4\uC2DC \uC778\uC99D\uD558\uC138\uC694.",
244
+ RetiredHexCredentialsRejected: "\uC774\uC804 hex \uC790\uACA9 \uC99D\uBA85\uC740 \uB354 \uC774\uC0C1 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 `01 login`\uC73C\uB85C \uB2E4\uC2DC \uC778\uC99D\uD558\uC138\uC694.",
245
245
  InvalidJsonObject: "`--{{label}}` \uC778\uC790\uB294 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
246
246
  InvalidJsonArray: "`--{{label}}` \uC778\uC790\uB294 JSON \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
247
247
  InvalidJsonValue: "`--{{label}}` \uC778\uC790\uC758 JSON \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {{value}}",
@@ -273,7 +273,7 @@ var CLI_I18N_EN = {
273
273
  PassApiKey: "pass `--api-key <token>`, or",
274
274
  SetEnvVars: "set `SOFTWARE_PUBLISHABLE_KEY` and `SOFTWARE_SECRET_KEY` environment variables.",
275
275
  InvalidApiKeyFormat: "Invalid API key format. Expected `sk01_` or `pat01_` token.",
276
- LegacyHexCredentialsRejected: "Legacy hex credentials are no longer accepted \u2014 run `01 login` to re-authenticate.",
276
+ RetiredHexCredentialsRejected: "Retired hex credentials are no longer accepted \u2014 run `01 login` to re-authenticate.",
277
277
  InvalidJsonObject: "--{{label}} must be a JSON object.",
278
278
  InvalidJsonArray: "--{{label}} must be a JSON array.",
279
279
  InvalidJsonValue: "Invalid JSON for --{{label}}: {{value}}",
@@ -477,7 +477,7 @@ function resolveClient(apiKeyFlag) {
477
477
  code: "credential_invalid",
478
478
  detail: {
479
479
  message: t("InvalidApiKeyFormat"),
480
- suggestion: t("LegacyHexCredentialsRejected")
480
+ suggestion: t("RetiredHexCredentialsRejected")
481
481
  }
482
482
  });
483
483
  }
@@ -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
  };
@@ -1419,19 +1558,51 @@ import { existsSync as existsSync2 } from "fs";
1419
1558
  import { spawn } from "child_process";
1420
1559
  import { fileURLToPath } from "url";
1421
1560
  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;
1561
+ function getStdioEntryCandidates(baseDir = __dirname) {
1562
+ const candidates = [
1563
+ {
1564
+ label: "packaged CLI artifact",
1565
+ path: resolve(baseDir, "mcp/stdio.js")
1566
+ }
1567
+ ];
1425
1568
  const roots = ["../../../..", "../../..", "../.."];
1426
- const entries = ["apps/mcp/dist/stdio.js", "apps/mcp/.xmcp/stdio.js"];
1569
+ const entries = [
1570
+ {
1571
+ label: "monorepo build output",
1572
+ path: "apps/mcp/dist/stdio.js"
1573
+ },
1574
+ {
1575
+ label: "xmcp build output",
1576
+ path: "apps/mcp/.xmcp/stdio.js"
1577
+ }
1578
+ ];
1427
1579
  for (const entry of entries) {
1428
1580
  for (const root of roots) {
1429
- const candidate = resolve(baseDir, root, entry);
1430
- if (existsSync2(candidate)) return candidate;
1581
+ candidates.push({
1582
+ label: entry.label,
1583
+ path: resolve(baseDir, root, entry.path)
1584
+ });
1431
1585
  }
1432
1586
  }
1587
+ return candidates;
1588
+ }
1589
+ function findStdioEntry(baseDir = __dirname) {
1590
+ for (const candidate of getStdioEntryCandidates(baseDir)) {
1591
+ if (existsSync2(candidate.path)) return candidate.path;
1592
+ }
1433
1593
  return null;
1434
1594
  }
1595
+ function formatMissingStdioEntryMessage(baseDir = __dirname) {
1596
+ const checked = getStdioEntryCandidates(baseDir).map((candidate) => ` - ${candidate.label}: ${candidate.path}`).join("\n");
1597
+ return [
1598
+ "MCP stdio entry not found.",
1599
+ "Checked:",
1600
+ checked,
1601
+ "Fix:",
1602
+ " - Monorepo checkout: run pnpm --filter mcp build.",
1603
+ " - Published CLI: reinstall or update @01.software/cli so dist/mcp/stdio.js is included."
1604
+ ].join("\n");
1605
+ }
1435
1606
  function createMcpEnv(baseEnv, client) {
1436
1607
  const env = {
1437
1608
  ...baseEnv,
@@ -1442,15 +1613,21 @@ function createMcpEnv(baseEnv, client) {
1442
1613
  return env;
1443
1614
  }
1444
1615
  function registerMcpCommands(program2) {
1445
- program2.command("mcp").description("Start MCP server over stdio").action(() => {
1616
+ program2.command("mcp").description("Start local MCP stdio server for trusted server-key workflows").addHelpText(
1617
+ "after",
1618
+ `
1619
+ Prerequisites:
1620
+ Run 01 login, or set SOFTWARE_PUBLISHABLE_KEY and SOFTWARE_SECRET_KEY.
1621
+ Local stdio exposes the full MCP tool surface; HTTP OAuth MCP uses the
1622
+ narrower remote surface documented in the web integration guide.
1623
+ In a monorepo checkout, build the stdio artifact first:
1624
+ pnpm --filter mcp build
1625
+ `
1626
+ ).action(() => {
1446
1627
  const client = resolveClient(program2.opts().apiKey);
1447
1628
  const stdioEntry = findStdioEntry();
1448
1629
  if (!stdioEntry) {
1449
- exitWithError(
1450
- new Error(
1451
- "MCP server not found. Ensure apps/mcp is built (pnpm --filter mcp build)."
1452
- )
1453
- );
1630
+ exitWithError(new Error(formatMissingStdioEntryMessage()));
1454
1631
  }
1455
1632
  const child = spawn(process.execPath, [stdioEntry], {
1456
1633
  env: createMcpEnv(process.env, client),