@01.software/cli 0.10.6 → 0.11.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/mcp/http.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  mcpServicePublicJwks,
13
13
  requestContext,
14
14
  runWithMcpTelemetry
15
- } from "./chunk-2ULP5WQH.js";
15
+ } from "./chunk-3TIDAOYP.js";
16
16
 
17
17
  // src/transport/http.ts
18
18
  import { createServer as createServer2 } from "http";
package/dist/mcp/stdio.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-2ULP5WQH.js";
3
+ } from "./chunk-3TIDAOYP.js";
4
4
 
5
5
  // src/transport/stdio.ts
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -760,14 +760,14 @@ var TOOL_POLICY_MANIFEST = {
760
760
  category: "mutation-order",
761
761
  oauthScope: MCP_SCOPES.write,
762
762
  consoleRole: "tenant-admin",
763
- consoleSurface: "POST /api/checkout",
763
+ consoleSurface: "POST /api/orders/checkout",
764
764
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
765
765
  },
766
766
  "create-order": {
767
767
  category: "mutation-order",
768
768
  oauthScope: MCP_SCOPES.write,
769
769
  consoleRole: "tenant-admin",
770
- consoleSurface: "POST /api/orders",
770
+ consoleSurface: "POST /api/orders/create",
771
771
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
772
772
  },
773
773
  "confirm-payment": {
@@ -782,7 +782,7 @@ var TOOL_POLICY_MANIFEST = {
782
782
  category: "mutation-order",
783
783
  oauthScope: MCP_SCOPES.write,
784
784
  consoleRole: "tenant-admin",
785
- consoleSurface: "PATCH /api/orders/{id}",
785
+ consoleSurface: "POST /api/orders/update",
786
786
  annotationPolicy: DESTRUCTIVE_IDEMPOTENT_MUTATION_ANNOTATION,
787
787
  exemptionReason: REASON_IDEMPOTENT_DESTRUCTIVE_UPDATE
788
788
  },
@@ -791,14 +791,14 @@ var TOOL_POLICY_MANIFEST = {
791
791
  category: "mutation-fulfillment",
792
792
  oauthScope: MCP_SCOPES.write,
793
793
  consoleRole: "tenant-admin",
794
- consoleSurface: "POST /api/orders/{id}/fulfillments",
794
+ consoleSurface: "POST /api/orders/create-fulfillment",
795
795
  annotationPolicy: NON_DESTRUCTIVE_MUTATION_ANNOTATION
796
796
  },
797
797
  "update-fulfillment": {
798
798
  category: "mutation-fulfillment",
799
799
  oauthScope: MCP_SCOPES.write,
800
800
  consoleRole: "tenant-admin",
801
- consoleSurface: "PATCH /api/fulfillments/{id}",
801
+ consoleSurface: "POST /api/orders/update-fulfillment",
802
802
  annotationPolicy: DESTRUCTIVE_IDEMPOTENT_MUTATION_ANNOTATION,
803
803
  exemptionReason: REASON_IDEMPOTENT_DESTRUCTIVE_UPDATE
804
804
  },
@@ -807,14 +807,14 @@ var TOOL_POLICY_MANIFEST = {
807
807
  category: "mutation-return",
808
808
  oauthScope: MCP_SCOPES.write,
809
809
  consoleRole: "tenant-admin",
810
- consoleSurface: "POST /api/returns",
810
+ consoleSurface: "POST /api/returns/create",
811
811
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
812
812
  },
813
813
  "update-return": {
814
814
  category: "mutation-return",
815
815
  oauthScope: MCP_SCOPES.write,
816
816
  consoleRole: "tenant-admin",
817
- consoleSurface: "PATCH /api/returns/{id}",
817
+ consoleSurface: "POST /api/returns/update",
818
818
  annotationPolicy: DESTRUCTIVE_IDEMPOTENT_MUTATION_ANNOTATION,
819
819
  exemptionReason: REASON_IDEMPOTENT_DESTRUCTIVE_UPDATE
820
820
  },
@@ -822,7 +822,7 @@ var TOOL_POLICY_MANIFEST = {
822
822
  category: "mutation-return",
823
823
  oauthScope: MCP_SCOPES.write,
824
824
  consoleRole: "tenant-admin",
825
- consoleSurface: "POST /api/returns/with-refund",
825
+ consoleSurface: "POST /api/returns/return-refund",
826
826
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
827
827
  },
828
828
  // ── Transaction mutations (mcp:write, tenant-admin) ──
@@ -830,7 +830,7 @@ var TOOL_POLICY_MANIFEST = {
830
830
  category: "mutation-transaction",
831
831
  oauthScope: MCP_SCOPES.write,
832
832
  consoleRole: "tenant-admin",
833
- consoleSurface: "PATCH /api/transactions/{id}",
833
+ consoleSurface: "POST /api/transactions/update",
834
834
  annotationPolicy: DESTRUCTIVE_NON_IDEMPOTENT_MUTATION_ANNOTATION
835
835
  },
836
836
  // ── Field-config mutations (mcp:write, tenant-admin) ──
@@ -1430,12 +1430,12 @@ var schema5 = {
1430
1430
  "delivered",
1431
1431
  "confirmed"
1432
1432
  ]).describe(
1433
- "New order status. Return-related statuses (return_requested, return_processing, returned) must be set via Return endpoints."
1433
+ "New operator-driven order status. The schema keeps SDK status values for compatibility, but the Console update endpoint rejects server-derived statuses (paid, canceled, returned, refunded); those must be set by payment/refund flows, and return-related statuses must be set via Return endpoints."
1434
1434
  )
1435
1435
  };
1436
1436
  var metadata5 = {
1437
1437
  name: "update-order",
1438
- description: "Update order status. Automatically adjusts stock on status changes (e.g., canceled restores stock).",
1438
+ description: "Update operator-driven order status. Console accepts transitions such as paid\u2192preparing/shipped/delivered/confirmed and rejects server-derived payment/refund statuses such as paid, canceled, returned, and refunded; the MCP schema keeps SDK status values for compatibility.",
1439
1439
  annotations: {
1440
1440
  title: "Update order status",
1441
1441
  readOnlyHint: false,
@@ -1689,12 +1689,12 @@ import { z as z11 } from "zod";
1689
1689
  var schema12 = {
1690
1690
  returnId: z11.string().min(1).describe("Return ID (required)"),
1691
1691
  status: z11.enum(["processing", "approved", "rejected", "completed"]).describe(
1692
- "New return status (required). Valid transitions: requested\u2192processing/rejected, processing\u2192approved/rejected, approved\u2192completed"
1692
+ "New operator-driven return status (required). The schema keeps SDK status values for compatibility, but Console accepts only requested\u2192processing/rejected and processing\u2192approved/rejected here; completed is server-derived and must be set by return-with-refund."
1693
1693
  )
1694
1694
  };
1695
1695
  var metadata12 = {
1696
1696
  name: "update-return",
1697
- description: "Update return status with FSM validation. Restores inventory on completion, reverts order status on rejection.",
1697
+ description: "Update operator-driven return status with FSM validation. Rejection can restore the order flow; completion and inventory restoration are handled by return-with-refund after provider-verified refund, while the MCP schema keeps SDK status values for compatibility.",
1698
1698
  annotations: {
1699
1699
  title: "Update return status",
1700
1700
  readOnlyHint: false,
@@ -1708,7 +1708,10 @@ async function updateReturn({
1708
1708
  }) {
1709
1709
  try {
1710
1710
  const client = getClient();
1711
- const result = await client.commerce.orders.updateReturn({ returnId, status });
1711
+ const result = await client.commerce.orders.updateReturn({
1712
+ returnId,
1713
+ status
1714
+ });
1712
1715
  return toolSuccess({ data: result });
1713
1716
  } catch (error) {
1714
1717
  return toolError(error);
@@ -1719,7 +1722,7 @@ async function updateReturn({
1719
1722
  var schema13 = ReturnWithRefundSchema.shape;
1720
1723
  var metadata13 = {
1721
1724
  name: "return-with-refund",
1722
- description: "Combined return + refund operation. Creates return, restores stock, cancels transaction, updates order status.",
1725
+ description: "Combined provider-verified return + refund operation. Creates a completed return, restores eligible stock, records the refund transaction, and advances the order to the returned state.",
1723
1726
  annotations: {
1724
1727
  title: "Return with refund",
1725
1728
  readOnlyHint: false,
@@ -1733,6 +1736,7 @@ async function returnWithRefund({
1733
1736
  reasonDetail,
1734
1737
  returnItems,
1735
1738
  refundAmount,
1739
+ returnShippingFee,
1736
1740
  pgPaymentId,
1737
1741
  paymentKey,
1738
1742
  refundReceiptUrl
@@ -1745,6 +1749,7 @@ async function returnWithRefund({
1745
1749
  reasonDetail,
1746
1750
  returnItems,
1747
1751
  refundAmount,
1752
+ returnShippingFee,
1748
1753
  pgPaymentId,
1749
1754
  paymentKey,
1750
1755
  refundReceiptUrl
@@ -2045,7 +2050,7 @@ var schema23 = {
2045
2050
  };
2046
2051
  var metadata23 = {
2047
2052
  name: "product-detail",
2048
- description: "Fetch full product detail by slug or id. Returns one resolver-ready product with variants, option slugs, option value slugs/media, brand, categories, tags, images, videos, and listing rollup, or null if missing/unpublished/wrong tenant/feature disabled.",
2053
+ description: "Fetch full product detail by slug or id. Returns one resolver-ready product with variants, option slugs, option value slugs/media, brand, categories, tags, images, videos, and listing rollup, or found:false with a reason if missing/unpublished/feature disabled. Permission/auth errors still throw.",
2049
2054
  annotations: {
2050
2055
  title: "Get product detail",
2051
2056
  readOnlyHint: true,
@@ -3675,8 +3680,9 @@ You can perform the "${goal}" task by following the patterns above.
3675
3680
  ### Product detail page (slug-based)
3676
3681
 
3677
3682
  \`\`\`typescript
3678
- const product = await client.commerce.product.detail({ slug })
3679
- if (!product) return notFound()
3683
+ const result = await client.commerce.product.detail({ slug })
3684
+ if (!result.found) return notFound()
3685
+ const product = result.product
3680
3686
  // product: { product, variants, options, brand, categories, tags, images, videos, listing }
3681
3687
  \`\`\`
3682
3688
 
@@ -3912,12 +3918,13 @@ const order = await client.commerce.orders.checkout({
3912
3918
  1. **Create Return** \u2192 \`create-return\` tool
3913
3919
  - Order must be \`delivered\` or \`confirmed\`
3914
3920
  - Specify returnItems and refundAmount
3915
- - Return status: requested \u2192 processing \u2192 approved \u2192 completed
3921
+ - Operator transitions: requested \u2192 processing/rejected, processing \u2192 approved/rejected
3922
+ - \`completed\` is server-derived and requires \`return-with-refund\`
3916
3923
 
3917
3924
  ### Option B: Return + Refund (atomic, recommended)
3918
3925
  1. **Return with Refund** \u2192 \`return-with-refund\` tool
3919
3926
  - Handles return + stock restoration + transaction update in one call
3920
- - Return immediately completed (bypasses FSM)
3927
+ - Sets the return to \`completed\` after provider-verified refund
3921
3928
  - Requires pgPaymentId and paymentKey for provider-verified refund
3922
3929
 
3923
3930
  ### Key Points
@@ -3933,8 +3940,9 @@ await client.commerce.orders.returnWithRefund({
3933
3940
  orderNumber: 'ORD-240101-001',
3934
3941
  reason: 'defective',
3935
3942
  reasonDetail: 'Product arrived damaged',
3936
- returnItems: [{ orderItem: 'oi-id', quantity: 1 }],
3943
+ returnItems: [{ orderItem: 'oi-id', quantity: 1, restockingFee: 500 }],
3937
3944
  refundAmount: 29900,
3945
+ returnShippingFee: 1500,
3938
3946
  pgPaymentId: 'pay_xxx',
3939
3947
  paymentKey: 'payment_key_xxx'
3940
3948
  })
@@ -5541,12 +5549,12 @@ const ret = await client.commerce.orders.createReturn({
5541
5549
  \`\`\`
5542
5550
 
5543
5551
  ### updateReturn()
5544
- Update return status.
5552
+ Update operator-driven return status. \`completed\` is server-derived and must go through \`returnWithRefund()\`.
5545
5553
 
5546
5554
  \`\`\`typescript
5547
5555
  const ret = await client.commerce.orders.updateReturn({
5548
5556
  returnId: 'return-id',
5549
- status: 'processing', // processing | approved | completed | rejected
5557
+ status: 'processing', // processing | approved | rejected
5550
5558
  })
5551
5559
  \`\`\`
5552
5560
 
@@ -5559,9 +5567,10 @@ const result = await client.commerce.orders.returnWithRefund({
5559
5567
  reason?: 'defective',
5560
5568
  reasonDetail?: 'Screen cracked on arrival',
5561
5569
  returnItems: [
5562
- { orderItem: 'order-item-id', quantity: 1 }
5570
+ { orderItem: 'order-item-id', quantity: 1, restockingFee?: 500 }
5563
5571
  ],
5564
5572
  refundAmount: 29900,
5573
+ returnShippingFee?: 1500,
5565
5574
  pgPaymentId: 'provider-payment-id', // required
5566
5575
  paymentKey: 'provider-payment-key', // required for provider refund
5567
5576
  refundReceiptUrl?: 'https://...',
@@ -6196,11 +6205,11 @@ var metadata49 = {
6196
6205
  function handler18() {
6197
6206
  return `# Webhooks
6198
6207
 
6199
- The platform dispatches HMAC-SHA256 signed webhook events to your registered URLs. Tenant developers own routing inside their webhook handler.
6208
+ The platform dispatches HMAC-SHA256 signed webhook events to registered URLs. Tenant developers own routing inside their webhook handler.
6200
6209
 
6201
6210
  ## Webhook Handling
6202
6211
 
6203
- Use the SDK \`handleWebhook\` helper to verify signatures. For customer auth events, use \`createCustomerAuthWebhookHandler\` to wire delivery behavior in your app.
6212
+ Use the SDK \`handleWebhook\` helper to verify signatures. For customer auth events, use \`createCustomerAuthWebhookHandler\`; for semantic events, use SDK guards before reading event-specific fields.
6204
6213
 
6205
6214
  \`\`\`typescript
6206
6215
  import { handleWebhook, createCustomerAuthWebhookHandler } from '@01.software/sdk/webhook'
@@ -6253,28 +6262,119 @@ export async function POST(request: Request) {
6253
6262
  }
6254
6263
  \`\`\`
6255
6264
 
6265
+ ## Commerce Notification Handler Example
6266
+
6267
+ \`\`\`typescript
6268
+ import {
6269
+ handleWebhook,
6270
+ isCommerceNotificationWebhookEvent,
6271
+ } from '@01.software/sdk/webhook'
6272
+
6273
+ function getWebhookSecret(): string {
6274
+ const secret = process.env.WEBHOOK_SECRET
6275
+ if (!secret) throw new Error('WEBHOOK_SECRET is required')
6276
+ return secret
6277
+ }
6278
+
6279
+ export async function POST(request: Request) {
6280
+ return handleWebhook(request, async (event) => {
6281
+ if (!isCommerceNotificationWebhookEvent(event)) return
6282
+
6283
+ const idempotencyKey = \`\${event.notification.intentId}:\${event.notification.dedupeKey}\`
6284
+ const processed = await processOnce(idempotencyKey, async () => {
6285
+ if (event.notification.event === 'orderPaid') {
6286
+ const orderId = event.notification.orderId ?? event.data.orderId
6287
+ if (orderId) await updateOrderWorkflow(orderId)
6288
+ }
6289
+
6290
+ if (event.notification.event === 'fulfillmentShipped') {
6291
+ const fulfillmentId =
6292
+ event.notification.fulfillmentId ?? event.data.fulfillmentId
6293
+ if (fulfillmentId) await updateFulfillmentWorkflow(fulfillmentId)
6294
+ }
6295
+ })
6296
+
6297
+ if (!processed) return
6298
+ }, {
6299
+ secret: getWebhookSecret(),
6300
+ })
6301
+ }
6302
+ \`\`\`
6303
+
6304
+ Back \`processOnce()\` with durable storage such as a database unique key or queue idempotency store; do not use process-local memory for webhook idempotency.
6305
+
6306
+ ## Headless Commerce Email Workers
6307
+
6308
+ For tenant-owned transactional email, use \`commerce.notification\` as the trigger and build a signed worker with \`createCommerceEmailWebhookHandler()\`. 01.software owns event timing, delivery retries, signing, and idempotency signals; the tenant worker owns template rendering, provider credentials, extra order/customer fetches via \`createServerClient\`, and final delivery through a tenant provider such as Resend.
6309
+
6310
+ Use \`getCommerceNotificationIdempotencyKey(event)\` or the \`idempotencyKey\` passed by \`createCommerceEmailWebhookHandler()\`, then guard provider sends with durable \`processOnce(idempotencyKey, ...)\` storage. The webhook payload is PII-light and is not enough for recipient data, so fetch recipient/template context with server SDK credentials. In the v1 headless path, do not store tenant ESP secrets in 01.software and do not ask 01.software to send directly through the tenant provider.
6311
+
6256
6312
  ## Webhook Payload Structure
6257
6313
 
6258
6314
  All webhook events share this envelope:
6259
6315
 
6260
6316
  \`\`\`typescript
6261
6317
  {
6262
- collection: string, // e.g. 'customers'
6263
- operation: string, // e.g. 'password-reset'
6264
- data: object, // event-specific payload
6318
+ collection: string,
6319
+ operation: string,
6320
+ data: object,
6321
+ eventType?: string,
6322
+ change?: object,
6323
+ notification?: object,
6324
+ timestamp?: string,
6325
+ deliveryId?: string,
6265
6326
  }
6266
6327
  \`\`\`
6267
6328
 
6268
6329
  ## Event Types
6269
6330
 
6331
+ ### Commerce Notification
6332
+
6333
+ V1 commerce notification webhooks use \`eventType: "commerce.notification"\` and \`operation: "notification"\`. The public SDK exports \`COMMERCE_NOTIFICATION_EVENT_TYPE\`, \`COMMERCE_NOTIFICATION_OPERATION\`, commerce notification types, and \`isCommerceNotificationWebhookEvent()\` from \`@01.software/sdk/webhook\`.
6334
+
6335
+ Canonical example (public operational identifiers only; no PII, payment/provider fields, metadata, tracking number, or tracking URL):
6336
+
6337
+ \`\`\`json
6338
+ {
6339
+ "eventType": "commerce.notification",
6340
+ "collection": "orders",
6341
+ "operation": "notification",
6342
+ "data": {
6343
+ "orderId": "order_123",
6344
+ "orderNumber": "ORD-1001",
6345
+ "status": "paid",
6346
+ "totalAmount": 5000,
6347
+ "currency": "KRW"
6348
+ },
6349
+ "notification": {
6350
+ "event": "orderPaid",
6351
+ "intentId": "intent_123",
6352
+ "dedupeKey": "orderPaid:order_123",
6353
+ "orderId": "order_123"
6354
+ },
6355
+ "timestamp": "2026-05-29T00:00:00.000Z",
6356
+ "deliveryId": "delivery_attempt_123"
6357
+ }
6358
+ \`\`\`
6359
+
6360
+ Use \`notification.intentId\` plus \`notification.dedupeKey\` for semantic idempotency. \`deliveryId\` is per delivery attempt / observability and may not be stable across semantic retries.
6361
+ Some normalized deliveries may include \`data.source\` or \`change\` metadata, but handlers should treat those fields as optional. Route from \`notification.orderId\`, \`notification.fulfillmentId\`, \`notification.returnId\`, or the matching typed \`data.*Id\` field.
6362
+
6363
+ V1 source subscription semantics:
6364
+
6365
+ - \`orderPaid\` and \`orderDelivered\` are delivered through \`orders\`-scoped endpoints.
6366
+ - \`fulfillmentShipped\` is delivered through \`fulfillments\`-scoped endpoints.
6367
+ - \`returnRequested\` and \`returnCompleted\` are delivered through \`returns\`-scoped endpoints.
6368
+ - Empty, unscoped, and all-collection endpoints do not receive v1 semantic commerce notifications.
6369
+
6270
6370
  ### Collection Order Changed
6271
6371
 
6272
6372
  Admin Panel manual ordering is delivered as a semantic collection update:
6273
6373
 
6274
6374
  - \`operation\` remains \`"update"\` for compatibility.
6275
6375
  - \`eventType\` is \`"collection.orderChanged"\`.
6276
- - \`change.scope\` identifies collection-level ordering versus join ordering.
6277
6376
  - SDK handlers should use \`isOrderChangedWebhookEvent()\` from \`@01.software/sdk/webhook\`.
6377
+ - \`change.scope\` identifies collection-level ordering versus join ordering.
6278
6378
  - Route on public semantics such as \`change.scope.collection\`, \`change.scope.field\`, and \`change.moved.id\`.
6279
6379
  - Do not branch on hidden Payload order fields or private backing collections; diagnostic fields may still be present.
6280
6380
  - Customer group member ordering is currently unsupported and does not emit a semantic order-change webhook.
@@ -6324,8 +6424,8 @@ Dispatched when a customer calls \`client.customer.forgotPassword(email)\`.
6324
6424
  customerId: string,
6325
6425
  email: string,
6326
6426
  name: string,
6327
- resetPasswordToken: string, // raw token to include in reset link
6328
- resetPasswordExpiresAt: string, // ISO 8601 expiry (1 hour from dispatch)
6427
+ resetPasswordToken: string,
6428
+ resetPasswordExpiresAt: string,
6329
6429
  }
6330
6430
  }
6331
6431
  \`\`\`
@@ -6340,11 +6440,13 @@ async function sendPasswordResetEmail(data: {
6340
6440
  resetPasswordToken: string
6341
6441
  resetPasswordExpiresAt: string
6342
6442
  }) {
6343
- const resetUrl = \`https://yourstore.com/reset-password?token=\${data.resetPasswordToken}\`
6443
+ const resetUrl = new URL('https://yourstore.com/reset-password')
6444
+ resetUrl.searchParams.set('token', data.resetPasswordToken)
6445
+
6344
6446
  await emailService.send({
6345
6447
  to: data.email,
6346
6448
  subject: 'Reset your password',
6347
- body: \`Reset link (expires \${data.resetPasswordExpiresAt}): \${resetUrl}\`,
6449
+ body: \`Reset link (expires \${data.resetPasswordExpiresAt}): \${resetUrl.toString()}\`,
6348
6450
  })
6349
6451
  }
6350
6452
  \`\`\`
@@ -6355,7 +6457,7 @@ Failed webhook deliveries are queued with automatic retries. Ensure your handler
6355
6457
 
6356
6458
  ## Webhook Configuration
6357
6459
 
6358
- Configure webhook URLs in the 01.software console under Tenant Settings > Webhooks. Multiple URLs can be registered; all receive every event.`;
6460
+ Configure webhook URLs in the 01.software console under Tenant Settings > Webhooks. Multiple URLs can be registered; scoped endpoints receive events for their configured source collection.`;
6359
6461
  }
6360
6462
 
6361
6463
  // src/resources/(docs)/product-detail.ts
@@ -6383,8 +6485,8 @@ const client = createClient({
6383
6485
  })
6384
6486
 
6385
6487
  const detail = await client.commerce.product.detail({ slug: 'my-product' })
6386
- if (!detail) return notFound()
6387
- const selection = resolveProductSelection(detail, {
6488
+ if (!detail.found) return notFound()
6489
+ const selection = resolveProductSelection(detail.product, {
6388
6490
  search: '?opt.option-color=color-black',
6389
6491
  })
6390
6492
  // selection.selectedVariant, selection.price, selection.stock, selection.media
@@ -6435,18 +6537,20 @@ export default async function ProductPage({ params }: { params: { slug: string }
6435
6537
  publishableKey: process.env.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY!,
6436
6538
  })
6437
6539
  const detail = await client.commerce.product.detail({ slug: params.slug })
6438
- if (!detail) return notFound()
6439
- return <ProductView detail={detail} />
6540
+ if (!detail.found) return notFound()
6541
+ return <ProductView detail={detail.product} />
6440
6542
  }
6441
6543
  \`\`\`
6442
6544
 
6443
- ## The \`null\` return contract
6545
+ ## The \`ProductDetailResult\` return contract
6546
+
6547
+ The endpoint returns 404 for \`not_found\`, \`not_published\`, or \`feature_disabled\`. The SDK maps those product-detail 404s to \`{ found: false, reason }\` so consumer UIs can render a standard 404, preview CTA, or feature-gating UI. Permission/auth errors, including 403 tenant mismatches, still throw typed SDK errors.
6444
6548
 
6445
- The endpoint returns 404 for \`not_found\`, \`not_published\`, \`tenant_mismatch\`, or \`feature_disabled\`. The SDK collapses all 404s to \`null\` so consumer UIs render one "not available" path.
6549
+ Successful product payloads expose inventory rollups without sentinel values: \`product.totalInventory\` is the tracked stock sum across non-unlimited variants, \`null\` when no variants are tracked, and \`product.hasUnlimitedVariant\` signals whether any variant is unlimited.
6446
6550
 
6447
6551
  ## Backend correlation
6448
6552
 
6449
- Log \`client.lastRequestId\` against backend logs \u2014 the endpoint records the exact 404 code alongside the request ID.`;
6553
+ Log \`client.lastRequestId\` against backend logs \u2014 the endpoint records the exact 404 reason alongside the request ID.`;
6450
6554
  }
6451
6555
 
6452
6556
  // src/server.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@01.software/cli",
3
- "version": "0.10.6",
3
+ "version": "0.11.0",
4
4
  "description": "CLI tool for 01.software platform",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  "commander": "^14.0.3",
23
23
  "picocolors": "^1.1.1",
24
24
  "zod": "^4.4.3",
25
- "@01.software/sdk": "^0.32.0"
25
+ "@01.software/sdk": "^0.33.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^22.19.18",