@cimplify/cli 0.2.8 → 0.3.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/{add-7PTWJV4F.mjs → add-ZJNXWN2B.mjs} +10 -10
- package/dist/assets-DMK2QOPD.mjs +208 -0
- package/dist/chunk-5IAYN7AJ.mjs +259 -0
- package/dist/{chunk-4SBJVRGM.mjs → chunk-C4M3DXKC.mjs} +3 -1
- package/dist/{chunk-NC3GKHDD.mjs → chunk-D7WMSGKK.mjs} +1 -1
- package/dist/{chunk-NZ4RG62Z.mjs → chunk-I3XQSSOT.mjs} +4 -1
- package/dist/{chunk-JJYWETGA.mjs → chunk-LS2VTSMQ.mjs} +8 -2
- package/dist/{chunk-H2HJQGFY.mjs → chunk-MHK4WVNF.mjs} +2392 -596
- package/dist/{chunk-JOUXICGV.mjs → chunk-MOZQODQS.mjs} +1 -1
- package/dist/{chunk-KPGRCXQY.mjs → chunk-QGBXGDA5.mjs} +5 -5
- package/dist/chunk-RRY3NEZZ.mjs +79 -0
- package/dist/{chunk-L6474RPL.mjs → chunk-RZQTHTXX.mjs} +1 -1
- package/dist/{chunk-4YSOZ6LY.mjs → chunk-YI7UMMM7.mjs} +1 -1
- package/dist/{chunk-UPEHLREA.mjs → chunk-YQVMG62Z.mjs} +3 -3
- package/dist/{deploy-6KVOROT3.mjs → deploy-UKOOPJAE.mjs} +8 -82
- package/dist/{dev-AQP6TMYK.mjs → dev-FD4PM3UD.mjs} +5 -5
- package/dist/dispatcher.mjs +34 -22
- package/dist/doctor-5LBLYT7M.mjs +314 -0
- package/dist/{domains-2ZQ7AG27.mjs → domains-JQMV6GAP.mjs} +5 -5
- package/dist/{env-FDBPGU3W.mjs → env-EVMYQUIK.mjs} +6 -6
- package/dist/explain-3KBMWL6M.mjs +223 -0
- package/dist/introspect-PFBI3JHO.mjs +8 -0
- package/dist/{link-P4K2HRXY.mjs → link-X3E4UZBF.mjs} +4 -4
- package/dist/{list-44MLIFI2.mjs → list-TE54SJIB.mjs} +3 -3
- package/dist/{login-RSKGT6GU.mjs → login-WSAW4BEA.mjs} +4 -4
- package/dist/{logout-ZFZLSJ32.mjs → logout-DJDINVDF.mjs} +2 -2
- package/dist/{logs-E2AGTDCF.mjs → logs-KUKGEXR2.mjs} +4 -4
- package/dist/{projects-5CJOZ3MT.mjs → projects-364HGWHO.mjs} +13 -11
- package/dist/repo-26N2CHF6.mjs +8 -0
- package/dist/{rollback-36O4NOEL.mjs → rollback-5YALPQXL.mjs} +5 -5
- package/dist/{status-6AT4HF63.mjs → status-W4HW3CX3.mjs} +4 -4
- package/dist/{unlink-5ABCT7B6.mjs → unlink-HIIW57OO.mjs} +2 -2
- package/dist/{update-6KEG7EWK.mjs → update-5MRKRVZC.mjs} +7 -7
- package/dist/{whoami-DIJZYZIN.mjs → whoami-LACWBSNL.mjs} +3 -3
- package/package.json +3 -3
- package/templates/storefront-auto/.claude/skills/cimplify-storefront/SKILL.md +145 -0
- package/templates/storefront-auto/.cursor/rules/cimplify-storefront.mdc +25 -0
- package/templates/storefront-auto/.env.example +22 -0
- package/templates/storefront-auto/AGENTS.md +95 -0
- package/templates/storefront-auto/CLAUDE.md +22 -0
- package/templates/storefront-auto/README.md +48 -0
- package/templates/storefront-auto/__tests__/brand.test.ts +4 -0
- package/templates/storefront-auto/__tests__/cart-flow.test.ts +4 -0
- package/templates/storefront-auto/__tests__/contract.test.ts +4 -0
- package/templates/storefront-auto/app/.well-known/ucp/route.ts +65 -0
- package/templates/storefront-auto/app/about/page.tsx +41 -0
- package/templates/storefront-auto/app/accessibility/page.tsx +11 -0
- package/templates/storefront-auto/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-auto/app/account/orders/page.tsx +21 -0
- package/templates/storefront-auto/app/account/page.tsx +22 -0
- package/templates/storefront-auto/app/account/settings/page.tsx +21 -0
- package/templates/storefront-auto/app/cart/page.tsx +9 -0
- package/templates/storefront-auto/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-auto/app/categories/[slug]/page.tsx +130 -0
- package/templates/storefront-auto/app/checkout/page.tsx +17 -0
- package/templates/storefront-auto/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-auto/app/collections/[slug]/page.tsx +130 -0
- package/templates/storefront-auto/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-auto/app/contact/page.tsx +54 -0
- package/templates/storefront-auto/app/error.tsx +61 -0
- package/templates/storefront-auto/app/faq/page.tsx +46 -0
- package/templates/storefront-auto/app/globals.css +47 -0
- package/templates/storefront-auto/app/layout.tsx +77 -0
- package/templates/storefront-auto/app/llms.txt/route.ts +94 -0
- package/templates/storefront-auto/app/login/page.tsx +17 -0
- package/templates/storefront-auto/app/not-found.tsx +39 -0
- package/templates/storefront-auto/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-auto/app/orders/[id]/page.tsx +24 -0
- package/templates/storefront-auto/app/page.tsx +94 -0
- package/templates/storefront-auto/app/privacy/page.tsx +44 -0
- package/templates/storefront-auto/app/products/[slug]/page.tsx +165 -0
- package/templates/storefront-auto/app/products/[slug]/product-detail.tsx +70 -0
- package/templates/storefront-auto/app/returns/page.tsx +11 -0
- package/templates/storefront-auto/app/robots.ts +18 -0
- package/templates/storefront-auto/app/search/page.tsx +38 -0
- package/templates/storefront-auto/app/search/search-client.tsx +7 -0
- package/templates/storefront-auto/app/shipping/page.tsx +16 -0
- package/templates/storefront-auto/app/shop/page.tsx +63 -0
- package/templates/storefront-auto/app/shop/shop-client.tsx +32 -0
- package/templates/storefront-auto/app/signup/page.tsx +17 -0
- package/templates/storefront-auto/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-auto/app/sitemap.ts +59 -0
- package/templates/storefront-auto/app/terms/page.tsx +44 -0
- package/templates/storefront-auto/app/track-order/page.tsx +24 -0
- package/templates/storefront-auto/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-auto/components/account-iframe.tsx +13 -0
- package/templates/storefront-auto/components/auto-hero.tsx +85 -0
- package/templates/storefront-auto/components/brand-marquee.tsx +27 -0
- package/templates/storefront-auto/components/cart-drawer.tsx +14 -0
- package/templates/storefront-auto/components/cart-pill.tsx +36 -0
- package/templates/storefront-auto/components/category-grid.tsx +28 -0
- package/templates/storefront-auto/components/category-tiles.tsx +104 -0
- package/templates/storefront-auto/components/collection-strip.tsx +45 -0
- package/templates/storefront-auto/components/feature-hero.tsx +84 -0
- package/templates/storefront-auto/components/fitment-finder.tsx +184 -0
- package/templates/storefront-auto/components/footer.tsx +153 -0
- package/templates/storefront-auto/components/header.tsx +45 -0
- package/templates/storefront-auto/components/hero.tsx +28 -0
- package/templates/storefront-auto/components/nav-link.tsx +20 -0
- package/templates/storefront-auto/components/newsletter.tsx +50 -0
- package/templates/storefront-auto/components/policy-page.tsx +49 -0
- package/templates/storefront-auto/components/promo-banner.tsx +41 -0
- package/templates/storefront-auto/components/providers.tsx +35 -0
- package/templates/storefront-auto/components/section-heading.tsx +37 -0
- package/templates/storefront-auto/components/service-brief.tsx +65 -0
- package/templates/storefront-auto/components/store-product-card.tsx +88 -0
- package/templates/storefront-auto/components/trade-in-cta.tsx +54 -0
- package/templates/storefront-auto/components/trust-bar.tsx +66 -0
- package/templates/storefront-auto/lib/brand.ts +744 -0
- package/templates/storefront-auto/lib/cart.ts +12 -0
- package/templates/storefront-auto/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-auto/next.config.ts +45 -0
- package/templates/storefront-auto/package.json +35 -0
- package/templates/storefront-auto/postcss.config.mjs +7 -0
- package/templates/storefront-auto/tsconfig.json +23 -0
- package/templates/storefront-auto/vitest.config.ts +9 -0
- package/templates/storefront-bakery/.env.example +2 -2
- package/templates/storefront-bakery/README.md +1 -1
- package/templates/storefront-bakery/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-bakery/next.config.ts +3 -0
- package/templates/storefront-bakery/package.json +1 -1
- package/templates/storefront-fashion/.env.example +2 -2
- package/templates/storefront-fashion/README.md +1 -1
- package/templates/storefront-fashion/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-fashion/next.config.ts +3 -0
- package/templates/storefront-fashion/package.json +1 -1
- package/templates/storefront-grocery/.env.example +2 -2
- package/templates/storefront-grocery/README.md +1 -1
- package/templates/storefront-grocery/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-grocery/next.config.ts +3 -0
- package/templates/storefront-grocery/package.json +1 -1
- package/templates/storefront-pharmacy/.claude/skills/cimplify-storefront/SKILL.md +145 -0
- package/templates/storefront-pharmacy/.cursor/rules/cimplify-storefront.mdc +25 -0
- package/templates/storefront-pharmacy/.env.example +22 -0
- package/templates/storefront-pharmacy/AGENTS.md +118 -0
- package/templates/storefront-pharmacy/CLAUDE.md +22 -0
- package/templates/storefront-pharmacy/README.md +87 -0
- package/templates/storefront-pharmacy/__tests__/brand.test.ts +4 -0
- package/templates/storefront-pharmacy/__tests__/cart-flow.test.ts +4 -0
- package/templates/storefront-pharmacy/__tests__/contract.test.ts +4 -0
- package/templates/storefront-pharmacy/app/.well-known/ucp/route.ts +65 -0
- package/templates/storefront-pharmacy/app/about/page.tsx +41 -0
- package/templates/storefront-pharmacy/app/accessibility/page.tsx +11 -0
- package/templates/storefront-pharmacy/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-pharmacy/app/account/orders/page.tsx +21 -0
- package/templates/storefront-pharmacy/app/account/page.tsx +22 -0
- package/templates/storefront-pharmacy/app/account/settings/page.tsx +21 -0
- package/templates/storefront-pharmacy/app/cart/page.tsx +9 -0
- package/templates/storefront-pharmacy/app/categories/[slug]/listing-client.tsx +19 -0
- package/templates/storefront-pharmacy/app/categories/[slug]/page.tsx +130 -0
- package/templates/storefront-pharmacy/app/checkout/page.tsx +17 -0
- package/templates/storefront-pharmacy/app/collections/[slug]/listing-client.tsx +20 -0
- package/templates/storefront-pharmacy/app/collections/[slug]/page.tsx +130 -0
- package/templates/storefront-pharmacy/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-pharmacy/app/contact/page.tsx +54 -0
- package/templates/storefront-pharmacy/app/error.tsx +61 -0
- package/templates/storefront-pharmacy/app/faq/page.tsx +46 -0
- package/templates/storefront-pharmacy/app/globals.css +47 -0
- package/templates/storefront-pharmacy/app/layout.tsx +77 -0
- package/templates/storefront-pharmacy/app/llms.txt/route.ts +94 -0
- package/templates/storefront-pharmacy/app/login/page.tsx +17 -0
- package/templates/storefront-pharmacy/app/not-found.tsx +39 -0
- package/templates/storefront-pharmacy/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-pharmacy/app/orders/[id]/page.tsx +24 -0
- package/templates/storefront-pharmacy/app/page.tsx +78 -0
- package/templates/storefront-pharmacy/app/privacy/page.tsx +44 -0
- package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +165 -0
- package/templates/storefront-pharmacy/app/products/[slug]/product-detail.tsx +70 -0
- package/templates/storefront-pharmacy/app/returns/page.tsx +11 -0
- package/templates/storefront-pharmacy/app/robots.ts +18 -0
- package/templates/storefront-pharmacy/app/search/page.tsx +38 -0
- package/templates/storefront-pharmacy/app/search/search-client.tsx +7 -0
- package/templates/storefront-pharmacy/app/shipping/page.tsx +16 -0
- package/templates/storefront-pharmacy/app/shop/page.tsx +63 -0
- package/templates/storefront-pharmacy/app/shop/shop-client.tsx +32 -0
- package/templates/storefront-pharmacy/app/signup/page.tsx +17 -0
- package/templates/storefront-pharmacy/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-pharmacy/app/sitemap.ts +59 -0
- package/templates/storefront-pharmacy/app/terms/page.tsx +44 -0
- package/templates/storefront-pharmacy/app/track-order/page.tsx +24 -0
- package/templates/storefront-pharmacy/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-pharmacy/components/account-iframe.tsx +13 -0
- package/templates/storefront-pharmacy/components/brand-marquee.tsx +27 -0
- package/templates/storefront-pharmacy/components/cart-drawer.tsx +14 -0
- package/templates/storefront-pharmacy/components/cart-pill.tsx +36 -0
- package/templates/storefront-pharmacy/components/category-grid.tsx +28 -0
- package/templates/storefront-pharmacy/components/category-tiles.tsx +104 -0
- package/templates/storefront-pharmacy/components/collection-strip.tsx +45 -0
- package/templates/storefront-pharmacy/components/feature-hero.tsx +84 -0
- package/templates/storefront-pharmacy/components/footer.tsx +153 -0
- package/templates/storefront-pharmacy/components/header.tsx +45 -0
- package/templates/storefront-pharmacy/components/health-brief.tsx +65 -0
- package/templates/storefront-pharmacy/components/hero.tsx +28 -0
- package/templates/storefront-pharmacy/components/nav-link.tsx +20 -0
- package/templates/storefront-pharmacy/components/newsletter.tsx +50 -0
- package/templates/storefront-pharmacy/components/pharmacy-hero.tsx +95 -0
- package/templates/storefront-pharmacy/components/policy-page.tsx +49 -0
- package/templates/storefront-pharmacy/components/promo-banner.tsx +41 -0
- package/templates/storefront-pharmacy/components/providers.tsx +35 -0
- package/templates/storefront-pharmacy/components/section-heading.tsx +37 -0
- package/templates/storefront-pharmacy/components/store-product-card.tsx +88 -0
- package/templates/storefront-pharmacy/components/symptom-finder.tsx +108 -0
- package/templates/storefront-pharmacy/components/trade-in-cta.tsx +54 -0
- package/templates/storefront-pharmacy/components/trust-bar.tsx +66 -0
- package/templates/storefront-pharmacy/components/urgent-ctas.tsx +117 -0
- package/templates/storefront-pharmacy/lib/brand.ts +790 -0
- package/templates/storefront-pharmacy/lib/cart.ts +12 -0
- package/templates/storefront-pharmacy/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-pharmacy/next.config.ts +45 -0
- package/templates/storefront-pharmacy/package.json +35 -0
- package/templates/storefront-pharmacy/postcss.config.mjs +7 -0
- package/templates/storefront-pharmacy/tsconfig.json +23 -0
- package/templates/storefront-pharmacy/vitest.config.ts +9 -0
- package/templates/storefront-restaurant/.env.example +2 -2
- package/templates/storefront-restaurant/README.md +1 -1
- package/templates/storefront-restaurant/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-restaurant/next.config.ts +3 -0
- package/templates/storefront-restaurant/package.json +1 -1
- package/templates/storefront-retail/.env.example +2 -2
- package/templates/storefront-retail/README.md +1 -1
- package/templates/storefront-retail/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-retail/next.config.ts +3 -0
- package/templates/storefront-retail/package.json +1 -1
- package/templates/storefront-services/.env.example +2 -2
- package/templates/storefront-services/README.md +1 -1
- package/templates/storefront-services/lib/cimplify-loader.ts +19 -0
- package/templates/storefront-services/next.config.ts +3 -0
- package/templates/storefront-services/package.json +1 -1
- package/dist/repo-E6SBKVDG.mjs +0 -8
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { promptYesNo } from './chunk-
|
|
2
|
+
import { promptYesNo } from './chunk-RZQTHTXX.mjs';
|
|
3
3
|
import { ENV_SCOPE, DOMAIN_TYPE } from './chunk-MXYUAJEW.mjs';
|
|
4
|
-
import { parseArgs, flagBool, flagString } from './chunk-
|
|
5
|
-
import { ApiClient } from './chunk-
|
|
6
|
-
import { readAuth, readProjectLink } from './chunk-
|
|
7
|
-
import { CliError, CLI_ERROR_CODE, info, dim, result, bold, success } from './chunk-
|
|
4
|
+
import { parseArgs, flagBool, flagString } from './chunk-C4M3DXKC.mjs';
|
|
5
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
6
|
+
import { readAuth, readProjectLink } from './chunk-LS2VTSMQ.mjs';
|
|
7
|
+
import { CliError, CLI_ERROR_CODE, info, dim, result, bold, success } from './chunk-I3XQSSOT.mjs';
|
|
8
8
|
|
|
9
9
|
// src/commands/domains.ts
|
|
10
10
|
var SUB_LS = "ls";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { formatEnvFile, parseEnvFile, parseKeyValueArg } from './chunk-
|
|
3
|
-
import { promptYesNo } from './chunk-
|
|
2
|
+
import { formatEnvFile, parseEnvFile, parseKeyValueArg } from './chunk-YI7UMMM7.mjs';
|
|
3
|
+
import { promptYesNo } from './chunk-RZQTHTXX.mjs';
|
|
4
4
|
import { ENV_SCOPE, PUBLIC_ENV_PREFIX, SECRET_MASK } from './chunk-MXYUAJEW.mjs';
|
|
5
|
-
import { parseArgs, flagString, flagBool } from './chunk-
|
|
6
|
-
import { ApiClient } from './chunk-
|
|
7
|
-
import { readAuth, readProjectLink } from './chunk-
|
|
8
|
-
import { CliError, CLI_ERROR_CODE, info, dim, result, bold, success } from './chunk-
|
|
5
|
+
import { parseArgs, flagString, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
6
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
7
|
+
import { readAuth, readProjectLink } from './chunk-LS2VTSMQ.mjs';
|
|
8
|
+
import { CliError, CLI_ERROR_CODE, info, dim, result, bold, success } from './chunk-I3XQSSOT.mjs';
|
|
9
9
|
import { promises } from 'fs';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { package_default } from './chunk-YQVMG62Z.mjs';
|
|
3
|
+
import { parseArgs } from './chunk-C4M3DXKC.mjs';
|
|
4
|
+
import { bold, dim, info, result, CliError, CLI_ERROR_CODE } from './chunk-I3XQSSOT.mjs';
|
|
5
|
+
|
|
6
|
+
// src/embedded-docs.ts
|
|
7
|
+
var TOPICS = [
|
|
8
|
+
{
|
|
9
|
+
"name": "brand",
|
|
10
|
+
"title": "Brand schema",
|
|
11
|
+
"description": "Single source of truth for visible strings (lib/brand.ts)",
|
|
12
|
+
"source_url": "https://cimplify.dev/docs/templates/brand",
|
|
13
|
+
"body": '## Field reference\n\n \n \n\n| Field | Required | Purpose |\n| --- | --- | --- |\n| `name` | yes | Full brand name; used in metadata, header, schema.org Organization |\n| `shortName` | yes | Compact form for tight spots (header, OG, app icons) |\n| `microTag` | yes | Tiny industry label shown beside the wordmark |\n| `description` | yes | Default OG / SEO description (\u2265 20 chars) |\n| `schemaType` | yes | schema.org Organization subtype (`Store`, `Bakery`, \u2026) |\n| `currency` | yes | ISO 4217 (e.g. `GHS`); used by every `formatPrice` |\n| `locale` | yes | BCP-47-ish, e.g. `en_GH` |\n| `contact` | yes | Address, email, phone, hours, country code |\n| `socials[]` | yes | Footer / contact chips with built-in icon keys |\n| `header.nav` | yes | Top nav links (label + href) |\n| `hero` | yes | Home hero: badge, title, subtitle, CTAs |\n| `trustItems?` | no | Hero trust strip (free shipping, warranty, \u2026) |\n| `brandStrip?` | no | "Authorised dealer for\u2026" strip |\n| `promo?` | no | Time-limited banner |\n| `terms` / `privacy` | yes | Standalone policy pages (eyebrow, sections, last updated) |\n| `shipping` / `returns` / `accessibility` | yes | Same shape as terms/privacy |\n| `newsletter` | yes | Footer signup copy |\n| `about` | yes | Eyebrow, title, paragraphs, sections |\n| `faq` | yes | Sectioned Q/A list |\n| `account` | yes | Login / signup / account eyebrows + titles (iframe owns the inputs) |\n| `contactPage` | yes | Eyebrow, title, body for `/contact` |\n| `trackOrder` | yes | Copy for the guest order lookup page |\n| `footer` | yes | Sitemap sections, blurb, optional "Powered by" |\n| `llms.summary` | yes | \u2265 20-char blurb that opens the `/llms.txt` index |\n| `mock.seed` | yes | Mock seed name (must match `SeedNameSchema`) |\n| `mock.businessId` | yes | Must start with `bus_` |\n\n \n\n \n\n## A complete example\n\n \n \n\n```ts title="lib/brand.ts (excerpt: the retail template)"\n\nexport const brand: Brand = {\n name: "Currents Electronics",\n shortName: "Currents",\n microTag: "ELECTRONICS",\n description:\n "Authorised dealer for Apple, Samsung, Sony, Bose. Same-day Accra delivery, two-year warranty.",\n schemaType: "Store",\n currency: "GHS",\n locale: "en_GH",\n\n contact: {\n email: "hello@currentselectronics.test",\n phone: "+233 244 000 000",\n phoneTel: "+233244000000",\n streetAddress: "Atomic Junction, East Legon",\n city: "Accra",\n countryCode: "GH",\n hours: "Mon\u2013Sat \xB7 9am\u20138pm",\n },\n\n socials: [\n { label: "Instagram", href: "https://instagram.com/currentselectronics", icon: "instagram" },\n { label: "WhatsApp", href: "https://wa.me/233244000000", icon: "whatsapp" },\n ],\n\n header: { nav: [\n { label: "Shop", href: "/shop" },\n { label: "Deals", href: "/categories/deals" },\n { label: "Support",href: "/faq" },\n ]},\n\n hero: {\n badge: "LAPTOPS \xB7 PHONES \xB7 AUDIO",\n title: "The tech you want, in stock today.",\n subtitle: "Same-day delivery in Accra. Two-year warranty on every product.",\n primaryCtaLabel: "Shop now",\n secondaryCtaLabel: "See deals",\n secondaryCtaHref: "/categories/deals",\n },\n\n // \u2026terms, privacy, shipping, returns, accessibility, about, faq, \u2026\n\n newsletter: {\n title: "New drops, real prices, in your inbox.",\n body: "One email a week. No newsletter chaff.",\n eyebrow: "The shortlist",\n },\n account: {\n loginEyebrow: "Welcome back", loginTitle: "Sign in to Currents",\n signupEyebrow: "Welcome", signupTitle: "Create your Currents account",\n accountEyebrow: "Your account", accountTitle: "Welcome back",\n },\n contactPage: { title: "Talk to a real human.", body: "We reply within a business day.", eyebrow: "Contact" },\n trackOrder: { title: "Where\'s my order?", body: "Enter your order number and email.", eyebrow: "Track an order" },\n footer: { sitemap: [/* \u2026 */], poweredBy: { label: "Cimplify", href: "https://app.cimplify.io" } },\n llms: { summary: "Authorised dealer for Apple, Samsung, Sony, Bose. Two-year warranty." },\n\n mock: { seed: "retail", businessId: "bus_currents_electronics" },\n};\n```\n\n \n\n \n\n## Validation: `assertBrand`\n\n \n\nTemplates run `assertBrand(brand)` in their `brand.test.ts`. Failures list every offending field with its dot-path; drift between the template and the schema is impossible to ship by accident.\n\n \n \n\n```ts title="__tests__/brand.test.ts"\n\ncreateBrandSuite({ brand });\n```\n\n \n\n \n\n## Industry-specific extensions\n\n \n\nNeed fields the base schema doesn\'t have (fashion\'s `lookbook`, services\' `bookingPolicy`)? Extend, don\'t fork. The base shape stays canonical and your custom fields type-check alongside it.\n\n \n \n\n```ts title="Extending BrandSchema"\n\nexport const FashionBrandSchema = BrandSchema.extend({\n lookbook: z.object({\n eyebrow: z.string(),\n title: z.string().min(1),\n drops: z.array(z.object({\n slug: z.string(),\n title: z.string(),\n heroImage: z.string().url(),\n })),\n }),\n});\n\nexport type FashionBrand = z.infer<typeof FashionBrandSchema>;\n```\n\n \n\n \n\n## Next\n\n \n \n- [**Templates overview**](/docs/templates)\n Six industry templates and what they ship\n\n \n- [**Customizing**](/docs/templates/customizing)\n Beyond brand: ejection and schema extensions\n'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "eject",
|
|
17
|
+
"title": "Customizing a template",
|
|
18
|
+
"description": "When and how to eject SDK components into your project",
|
|
19
|
+
"source_url": "https://cimplify.dev/docs/templates/customizing",
|
|
20
|
+
"body": '## The customisation tiers\n\n \n \n\n| Tier | When | Cost |\n| --- | --- | --- |\n| Edit `brand.ts` | Copy, links, contact, hero, FAQ, policies | Free updates from the SDK forever |\n| Edit `globals.css @theme` | Palette, radius, fonts | Free updates from the SDK forever |\n| `classNames` overrides | Restyle a slot inside an SDK component | Free updates; no fork |\n| Eject a component | Restructure JSX, change behaviour, add merchant logic | You own the file; no auto-updates |\n| Extend `BrandSchema` | Industry-specific fields (lookbook, booking policy) | Type-checked, validated, schema co-evolves |\n\n \n\n \n\n## Edit content via `brand.ts`\n\n \n\nHardcoding a string in a page or component is the wrong move. Hoist it into the brand object first; everything that reads from `brand` stays consistent across pages, sitemap, and metadata.\n\n \n \n\n```tsx\n// \u274C wrong: hardcoded in a component\n<h1>Akua\'s Bakery</h1>\n\n// \u2705 right: sourced from the brand contract\n<h1>{brand.hero.title}</h1>\n```\n\n \n\n \n\n## Eject a component\n\n \n\nThe SDK ships ~67 ejectable components in its registry (cards, selectors, full pages, primitives). Eject when a `classNames` override can\'t reach the part you need to change, or when you\'re restructuring beyond what the component\'s props support.\n\n \n \n\n```bash\n# Browse the registry\ncimplify list\n\n# Eject one\ncimplify add cart-summary\n# \u2192 writes ./components/cart-summary.tsx, owned by you\n\n# Replace the SDK import in your page\n# import { CartSummary } from "@cimplify/sdk/react"\n# import { CartSummary } from "@/components/cart-summary"\n```\n\n \n \n\nOnce ejected, the file is yours. SDK upgrades won\'t touch it; you trade off auto-bugfixes for full control.\n\n \n \n\n**Don\'t reinvent the customizer.** Variant selection, add-on math, bundle pricing, and composite mode-switching took many iterations to get right. Eject `variant-selector`, `composite-selector`, `bundle-selector`, or `add-on-selector` and re-style; don\'t rewrite the cart payload contract. See `AGENTS.md` for the full doctrine.\n\n \n\n \n\n## Extend the brand schema\n\n \n\nIndustry-specific fields belong on the schema, not in scattered component props. Use `BrandSchema.extend` so your additions get the same boot-time validation as the canonical fields.\n\n \n \n\n```ts title="lib/schema.ts (services template)"\n\nexport const ServicesBrandSchema = BrandSchema.extend({\n bookingPolicy: z.object({\n cancellationWindowHours: z.number().int().positive(),\n depositPercent: z.number().int().min(0).max(100),\n rescheduleNoticeHours: z.number().int().positive(),\n }),\n});\n\nexport type ServicesBrand = z.infer<typeof ServicesBrandSchema>;\n```\n\n \n \n \n\n```ts title="lib/brand.ts"\n\nexport const brand: ServicesBrand = {\n // \u2026all the standard Brand fields\u2026\n bookingPolicy: {\n cancellationWindowHours: 24,\n depositPercent: 25,\n rescheduleNoticeHours: 12,\n },\n};\n```\n\n \n \n \n\n```ts title="__tests__/brand.test.ts: assert against the extended schema"\n\ndescribe("brand", () => {\n it("conforms to the services brand contract", () => {\n expect(ServicesBrandSchema.safeParse(brand).success).toBe(true);\n });\n});\n```\n\n \n\n \n\n## Add a new section\n\n \n\n1. Build the section as a Server Component in `components/`.\n2. Read merchant-specific copy from `brand`; extend the schema if the field isn\'t there.\n3. Wrap any client interactivity in a `*-client.tsx` island behind `<Suspense>` to keep the chrome cached.\n4. Compose into the page (`app/page.tsx`, `app/products/[slug]/page.tsx`, \u2026).\n5. Run `bun run check`.\n\n \n\n## Wire a Server Action\n\n \n \n\n```ts\n"use server";\n\nexport async function cancelMyOrder(orderId: string) {\n const r = await getServerClient().orders.cancel(orderId, "customer requested");\n if (!r.ok) return { ok: false as const, message: r.error.message };\n\n await revalidateOrders();\n return { ok: true as const };\n}\n```\n\n \n\n \n\n## Don\'ts\n\n \n\n- Hardcode strings in pages or components.\n- Disable `cacheComponents: true` to silence a warning. Wrap in `<Suspense>` instead.\n- Use `unstable_cache`. Next 16\'s canonical primitive is `\'use cache\'`.\n- Bypass `getServerClient()` by instantiating `createCimplifyClient` directly in a Server Component; you\'ll lose per-request memoisation.\n\n \n\n## Next\n\n \n \n- [**Brand schema**](/docs/templates/brand)\n Field reference and a full example\n\n \n- [**Testing**](/docs/testing)\n Catch ejections that broke the contract\n'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "products",
|
|
24
|
+
"title": "Catalogue \u2014 Single product",
|
|
25
|
+
"description": "Product shape, single-product fetch, by id or slug",
|
|
26
|
+
"source_url": "https://cimplify.dev/docs/sdk/catalogue#single-product",
|
|
27
|
+
"body": "## Single product\nBoth `getProductById` and `getProductBySlug` resolve to the same endpoint; the server accepts either a UUID or a slug.\n\n \n \n\n```ts\nconst byId = await client.catalogue.getProductById('prod_xxx')\nconst bySlug = await client.catalogue.getProductBySlug('studio-tee-natural')\n\nif (bySlug.ok) {\n const product = bySlug.value // ProductWithDetails\n console.log(product.id, product.name)\n console.log(product.variants)\n console.log(product.add_ons)\n console.log(product.images)\n}\n```\n"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "variants",
|
|
31
|
+
"title": "Catalogue \u2014 Variants",
|
|
32
|
+
"description": "Variant axes, picking by axis selections",
|
|
33
|
+
"source_url": "https://cimplify.dev/docs/sdk/catalogue#variants",
|
|
34
|
+
"body": "## Variants\n```ts\nconst variants = await client.catalogue.getVariants('prod_xxx')\nconst single = await client.catalogue.getVariantById('prod_xxx', 'var_yyy')\n\n// Find a variant by axis selections (size: M, color: blue)\nconst found = await client.catalogue.getVariantByAxisSelections('prod_xxx', {\n size: 'M',\n color: 'blue',\n})\n\nif (found.ok && found.value) {\n console.log(found.value.id, found.value.default_price)\n}\n```\n"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "bundles",
|
|
38
|
+
"title": "Catalogue \u2014 Bundles and composites",
|
|
39
|
+
"description": "Fixed bundles AND configurable composites (same catalogue section)",
|
|
40
|
+
"source_url": "https://cimplify.dev/docs/sdk/catalogue#bundles-and-composites",
|
|
41
|
+
"body": "## Bundles and composites\n**Bundles** are fixed product groupings with a single combined price. **Composites** are build-your-own: the customer picks one option per component and the price is computed from those selections.\n\n \n \n\n```ts\n// Bundles\nconst bundles = await client.catalogue.getBundles()\nconst bundle = await client.catalogue.getBundleById('bun_xxx')\n\n// Composites\nconst composites = await client.catalogue.getComposites()\nconst composite = await client.catalogue.getCompositeById('comp_xxx')\n\n// Price a composite from a set of selections\nconst price = await client.catalogue.calculateCompositePrice(\n 'comp_xxx',\n [\n { component_id: 'comp_base', selected_option_id: 'opt_classic' },\n { component_id: 'comp_milk', selected_option_id: 'opt_oat' },\n ],\n 'loc_main',\n)\n```\n"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "composites",
|
|
45
|
+
"title": "Catalogue \u2014 Bundles and composites",
|
|
46
|
+
"description": "Alias of `bundles` \u2014 customer-configurable bundles",
|
|
47
|
+
"source_url": "https://cimplify.dev/docs/sdk/catalogue#bundles-and-composites",
|
|
48
|
+
"body": "## Bundles and composites\n**Bundles** are fixed product groupings with a single combined price. **Composites** are build-your-own: the customer picks one option per component and the price is computed from those selections.\n\n \n \n\n```ts\n// Bundles\nconst bundles = await client.catalogue.getBundles()\nconst bundle = await client.catalogue.getBundleById('bun_xxx')\n\n// Composites\nconst composites = await client.catalogue.getComposites()\nconst composite = await client.catalogue.getCompositeById('comp_xxx')\n\n// Price a composite from a set of selections\nconst price = await client.catalogue.calculateCompositePrice(\n 'comp_xxx',\n [\n { component_id: 'comp_base', selected_option_id: 'opt_classic' },\n { component_id: 'comp_milk', selected_option_id: 'opt_oat' },\n ],\n 'loc_main',\n)\n```\n"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "add-ons",
|
|
52
|
+
"title": "Catalogue \u2014 Add-ons",
|
|
53
|
+
"description": "Product add-ons / customizations (gift wrap, engraving, \u2026)",
|
|
54
|
+
"source_url": "https://cimplify.dev/docs/sdk/catalogue#add-ons",
|
|
55
|
+
"body": "## Add-ons\n```ts\nconst addOns = await client.catalogue.getAddOns('prod_xxx')\nif (addOns.ok) {\n for (const group of addOns.value) {\n console.log(group.name, group.options)\n }\n}\n```\n"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "quotes",
|
|
59
|
+
"title": "Catalogue \u2014 Price quotes",
|
|
60
|
+
"description": "Price quotes \u2014 quote-first pricing flow before add-to-cart",
|
|
61
|
+
"source_url": "https://cimplify.dev/docs/sdk/catalogue#price-quotes",
|
|
62
|
+
"body": "## Price quotes\nWhenever variants, add-ons, bundle picks, or composite picks affect price, fetch a quote first and pass `quote_id` to `cart.addItem`. Quotes have an `expires_at`; refresh if the user dwells.\n\n \n \n\n```ts\nconst quote = await client.catalogue.fetchQuote({\n product_id: 'prod_xxx',\n variant_id: 'var_yyy',\n quantity: 2,\n add_on_option_ids: ['addopt_oat_milk'],\n location_id: 'loc_main',\n})\n\nif (!quote.ok) {\n console.error(quote.error.code, quote.error.message)\n return\n}\n\nawait client.cart.addItem({\n item_id: 'prod_xxx',\n quantity: 2,\n variant_id: 'var_yyy',\n add_on_options: ['addopt_oat_milk'],\n quote_id: quote.value.quote_id,\n})\n\n// Refresh an aging quote\nconst refreshed = await client.catalogue.refreshQuote({\n quote_id: quote.value.quote_id,\n quantity: 3,\n})\nif (refreshed.ok) {\n console.log(refreshed.value.quote.quote_id)\n}\n```\n"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "discounts",
|
|
66
|
+
"title": "Catalogue \u2014 Discounts",
|
|
67
|
+
"description": "Deals and promotional pricing",
|
|
68
|
+
"source_url": "https://cimplify.dev/docs/sdk/catalogue#discounts",
|
|
69
|
+
"body": "## Discounts\nValidate a code at the line / subtotal level before applying it to the cart. Order subtotal is passed as a `Money` string.\n\n \n \n\n```ts\nconst r = await client.catalogue.validateDiscountCode('WELCOME10', '49.99', 'loc_main')\nif (r.ok) {\n console.log(r.value.is_valid, r.value.discount_amount)\n}\n```\n"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "cart",
|
|
73
|
+
"title": "Cart",
|
|
74
|
+
"description": "Flat add-to-cart payload + quote-then-add flow",
|
|
75
|
+
"source_url": "https://cimplify.dev/docs/sdk/cart",
|
|
76
|
+
"body": "## Add an item\n\n \n\nThe add-to-cart payload is **flat**. There is no nested `configuration` wrapper and no `productId` field; everything sits at the top of the body.\n\n \n \n\n```ts\nconst added = await client.cart.addItem({\n item_id: 'prod_studio-tee-natural',\n quantity: 1,\n variant_id: 'var_studio-tee-natural_size_s',\n add_on_options: ['addopt_gift_wrap'],\n special_instructions: 'Please ship without invoice.',\n})\n\nif (!added.ok) {\n console.error(added.error.code, added.error.message)\n return\n}\n\nconsole.log(added.value.cart.id)\n```\n\n \n\n \n\n## Read the cart\n\n \n \n\n```ts\n\nconst r = await client.cart.get()\nif (!r.ok) return\n\nconst cart = r.value\nconsole.log(cart.items.length)\nconsole.log(cart.pricing.currency) // \"GHS\"\nconsole.log(cart.pricing.subtotal) // Money (branded string)\nconsole.log(cart.pricing.total_price) // Money\n\n// Money is a string. Always parse before math, format before display.\nconst subtotal = parsePrice(cart.pricing.subtotal)\nif (subtotal !== 0) {\n console.log(formatPrice(subtotal, cart.pricing.currency)) // \"GH\u20B529.99\"\n}\n```\n\n \n\n \n\n## Update quantity / remove / clear\n\n \n \n\n```ts\n// You need the cart-item id from cart.items[i].id (NOT the product id)\nawait client.cart.updateQuantity('ci_xxx', 3)\nawait client.cart.removeItem('ci_xxx')\nawait client.cart.clear()\n```\n\n \n\n \n\n## Coupons\n\n \n \n\n```ts\nconst apply = await client.cart.applyCoupon('WELCOME10')\nif (!apply.ok) {\n // Codes you'll see: COUPON_INVALID, COUPON_EXPIRED, COUPON_MIN_NOT_MET\n console.error(apply.error.code)\n}\n\nawait client.cart.removeCoupon()\n\n// Custom idempotency key (otherwise the SDK auto-generates one)\nawait client.cart.applyCoupon('WELCOME10', { idempotencyKey: 'apply-welcome10-once' })\n```\n\n \n\n \n\n## Convenience reads\n\n \n \n\n```ts\nconst count = await client.cart.getCount() // Result<number>\nconst total = await client.cart.getTotal() // Result<string>\nconst items = await client.cart.getItems() // Result<CartItem[]>\nconst summary = await client.cart.getSummary() // Result<CartSummary>\nconst empty = await client.cart.isEmpty() // Result<boolean>\nconst has = await client.cart.hasItem('prod_xxx', 'var_yyy') // Result<boolean>\nconst found = await client.cart.findItem('prod_xxx', 'var_yyy') // Result<CartItem | undefined>\n```\n\n \n\n \n\n## Reorder from a past order\n\n \n\nWalks every line item on the order and adds it to the current cart. Returns added/failed lists; partial success is normal (e.g. a discontinued variant).\n\n \n \n\n```ts\nconst r = await client.cart.reorderFromOrder('ord_xxx')\nif (!r.ok) return\n\nconsole.log(r.value.added) // [{ item_id }, ...]\nconsole.log(r.value.failed) // [{ item_id, error: CimplifyError }, ...]\n```\n\n \n\n \n\n## addItem payload\n\n \n \n\n| Field | Type | Notes |\n| --- | --- | --- |\n| `item_id` | `string` | Required. Product ID. |\n| `quantity` | `number` | Required. |\n| `variant_id` | `string` | Selected variant. |\n| `add_on_options` | `string[]` | Selected add-on option IDs. |\n| `special_instructions` | `string` | Free-form note. |\n| `bundle_selections` | `BundleSelection[]` | Required when item is a bundle. |\n| `composite_selections` | `CompositeSelection[]` | Required when item is a composite. |\n| `quote_id` | `string` | Pre-computed quote from `catalogue.fetchQuote`. Locks the price. |\n\n \n\n \n\n## Method reference\n\n \n \n\n| Method | Returns |\n| --- | --- |\n| `get()` | `Result<UICart>` |\n| `getItems()` | `Result<CartItem[]>` |\n| `getCount()` | `Result<number>` |\n| `getTotal()` | `Result<string>` |\n| `getSummary()` | `Result<CartSummary>` |\n| `addItem(input, opts?)` | `Result<CartMutationResult>` |\n| `updateQuantity(cartItemId, qty)` | `Result<CartMutationResult>` |\n| `removeItem(cartItemId)` | `Result<CartMutationResult>` |\n| `clear()` | `Result<CartMutationResult>` |\n| `applyCoupon(code, opts?)` | `Result<CartMutationResult>` |\n| `removeCoupon()` | `Result<CartMutationResult>` |\n| `reorderFromOrder(orderId)` | `Result<ReorderResult>` |\n| `isEmpty()` | `Result<boolean>` |\n| `hasItem(productId, variantId?)` | `Result<boolean>` |\n| `findItem(productId, variantId?)` | `Result<CartItem \\| undefined>` |\n\n \n\n \n\n## Related\n\n \n \n- [**Catalogue**](/docs/sdk/catalogue)\n Fetch products and price quotes before adding\n\n \n- [**Checkout**](/docs/sdk/checkout)\n Convert the cart into a paid order\n\n \n- [**Scheduling**](/docs/sdk/scheduling)\n Pick a slot for service bookings before checkout\n\n \n- [**Money type**](/docs/concepts/money)\n parsePrice / formatPrice for cart totals\n"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "checkout",
|
|
80
|
+
"title": "Checkout",
|
|
81
|
+
"description": "Flat ProcessArgs body for /checkout, payment methods",
|
|
82
|
+
"source_url": "https://cimplify.dev/docs/sdk/checkout",
|
|
83
|
+
"body": "## Process a checkout\n\n \n \n\n```ts\nconst r = await client.checkout.process({\n cart_id: cart.id,\n location_id: 'loc_main',\n order_type: 'pickup', // 'delivery' | 'pickup' | 'dine-in' | 'walk-in'\n payment_method: 'mobile_money', // 'mobile_money' | 'card'\n customer: {\n name: 'Jane Doe',\n email: 'jane@example.com',\n phone: '+233244000000',\n save_details: true,\n },\n address_info: {\n pickup_time: '2026-05-08T15:00:00Z',\n },\n mobile_money_details: {\n phone_number: '+233244000000',\n provider: 'mtn',\n },\n special_instructions: 'No straw, please.',\n})\n\nif (!r.ok) {\n console.error(r.error.code, r.error.message)\n return\n}\n\nconst result = r.value\nconsole.log(result.order_id, result.order_number)\nconsole.log(result.payment_status) // 'pending' | 'success' | 'failed' | ...\nconsole.log(result.requires_authorization)\nconsole.log(result.next_action) // discriminated union, see below\n```\n\n \n\n \n\n## bill_token: guest order lookups\n\n \n\nOn a successful guest checkout the response carries a `bill_token`. The SDK persists it client-side keyed by `order_id`, then attaches it to every subsequent ` orders.get` / `checkout.pollPaymentStatus` call. You do not need to thread it through manually; it just works in the same browser session.\n\n \n \n\n```ts\nif (r.ok && r.value.bill_token) {\n // Already cached on the client; this read is purely for your records.\n // No need to pass it back in subsequent calls.\n console.log(r.value.bill_token)\n}\n```\n\n \n\n \n\n## next_action: branch on what to do next\n\n \n \n\n```ts\nswitch (result.next_action?.type) {\n case 'none':\n // Payment captured. Show a confirmation screen.\n break\n\n case 'redirect':\n window.location.href = result.next_action.authorization_url\n break\n\n case 'card_popup':\n openProviderPopup({\n provider: result.next_action.provider,\n clientSecret: result.next_action.client_secret,\n publicKey: result.next_action.public_key,\n })\n break\n\n case 'authorization':\n // Collect OTP / PIN / phone / birthday / address from the user, then submit\n promptForAuthorization(result.next_action.authorization_type, result.next_action.display_text)\n break\n\n case 'poll':\n pollUntilTerminal(result.order_id)\n break\n}\n```\n\n \n\n \n\n## submitAuthorization\n\n \n\nWhen the customer completes an OTP / PIN / etc., POST it back through the SDK. The response is the same `CheckoutResult` shape, with an updated `next_action`.\n\n \n \n\n```ts\nconst r = await client.checkout.submitAuthorization({\n order_id: 'ord_xxx',\n authorization_type: 'otp',\n authorization_value: '123456',\n})\n\nif (!r.ok) {\n console.error(r.error.code, r.error.message)\n return\n}\n\nif (r.value.next_action?.type === 'poll') {\n await pollUntilTerminal(r.value.order_id)\n}\n```\n\n \n\n \n\n## pollPaymentStatus\n\n \n \n\n```ts\nasync function pollUntilTerminal(orderId: string, intervalMs = 2000, maxAttempts = 30) {\n for (let i = 0; i < maxAttempts; i++) {\n const r = await client.checkout.pollPaymentStatus(orderId)\n if (!r.ok) return r\n\n const status = r.value.status\n if (status === 'success' || status === 'failed') {\n return r\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs))\n }\n}\n```\n\n \n\n \n\n## Attach a customer to a guest order\n\n \n \n\n```ts\n// After a guest checkout, attach customer info to the order (uses bill_token automatically)\nconst r = await client.checkout.updateOrderCustomer('ord_xxx', {\n name: 'Jane Doe',\n email: 'jane@example.com',\n phone: '+233244000000',\n save_details: false,\n})\n```\n\n \n\n \n\n## Cross-currency checkout\n\n \n\nSet `pay_currency` on the request and the SDK locks an FX quote for you behind the scenes; the `fx` field on the response shows the locked rate.\n\n \n \n\n```ts\nconst r = await client.checkout.process({\n cart_id: cart.id,\n customer: { /* ... */ },\n order_type: 'pickup',\n payment_method: 'card',\n address_info: {},\n pay_currency: 'USD',\n})\n\nif (r.ok && r.value.fx) {\n console.log(r.value.fx.base_amount, '\u2192', r.value.fx.pay_amount)\n console.log(r.value.fx.rate, r.value.fx.quote_id)\n}\n```\n\n \n\n \n\n## CheckoutFormData (request body)\n\n \n \n\n| Field | Type | Notes |\n| --- | --- | --- |\n| `cart_id` | `string` | Required. |\n| `customer` | `CheckoutCustomerInfo` | Required. `name, email, phone, save_details`. |\n| `order_type` | `'delivery' \\| 'pickup' \\| 'dine-in' \\| 'walk-in'` | Required. |\n| `payment_method` | `'mobile_money' \\| 'card'` | Required. |\n| `address_info` | `CheckoutAddressInfo` | Required (can be empty for pickup). |\n| `location_id` | `string` | Multi-location businesses. |\n| `mobile_money_details` | `MobileMoneyDetails` | Required when `payment_method = 'mobile_money'`. |\n| `special_instructions` | `string` | Free-form note for the order. |\n| `idempotency_key` | `string` | Auto-generated if omitted. |\n| `pay_currency` | `CurrencyCode` | Triggers FX quoting if different from cart currency. |\n| `fx_quote_id` | `string` | Pre-locked quote (otherwise the SDK locks one). |\n| `metadata` | `Record<string, unknown>` | Round-tripped to webhooks. |\n\n \n\n \n\n## Method reference\n\n \n \n\n| Method | Returns |\n| --- | --- |\n| `process(data)` | `Result<CheckoutResult>` |\n| `submitAuthorization(input)` | `Result<CheckoutResult>` |\n| `pollPaymentStatus(orderId)` | `Result<PaymentStatusResponse>` |\n| `updateOrderCustomer(orderId, customer)` | `Result<Order>` |\n| `verifyPayment(orderId)` | `Result<Order>` |\n\n \n\n \n\n## Related\n\n \n \n- [**Cart**](/docs/sdk/cart)\n The `cart_id` input comes from here\n\n \n- [**Orders**](/docs/sdk/orders)\n Read the order created by checkout.process\n\n \n- [**FX**](/docs/sdk/fx)\n Rates and locked quotes for cross-currency pay\n\n \n- [**Error handling**](/docs/sdk/errors)\n PAYMENT_FAILED, AUTH_INCOMPLETE, recovery flows\n"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "orders",
|
|
87
|
+
"title": "Orders",
|
|
88
|
+
"description": "Order shape and status flow",
|
|
89
|
+
"source_url": "https://cimplify.dev/docs/sdk/orders",
|
|
90
|
+
"body": "## List orders\n\n \n \n\n```ts\nconst r = await client.orders.list({ limit: 20 })\nif (!r.ok) {\n console.error(r.error.code, r.error.message)\n return\n}\n\nconsole.log(r.value.length)\nfor (const order of r.value) {\n console.log(order.id, order.order_number, order.status)\n}\n```\n\n \n\n \n\n## Filter by status\n\n \n \n\n```ts\nconst pending = await client.orders.list({ status: 'pending' })\nconst completed = await client.orders.list({ status: 'completed', limit: 50 })\n\n// Convenience wrappers\nconst recent = await client.orders.getRecent(5) // last 5\nconst cancelled = await client.orders.getByStatus('cancelled')\n```\n\n \n\n \n\n## Get a single order\n\n \n\nFor guest orders the SDK appends the cached `bill_token` automatically; the lookup works in the same browser session that placed the order, with no auth.\n\n \n \n\n```ts\n\nconst r = await client.orders.get('ord_xxx')\nif (!r.ok) return\n\nconst order = r.value\nconsole.log(order.status)\nconsole.log(order.items.length)\nconsole.log(order.total_price) // Money string\nconsole.log(formatPrice(parsePrice(order.total_price), order.currency))\n```\n\n \n\n \n\n## Cancel an order\n\n \n \n\n```ts\nconst cancelled = await client.orders.cancel('ord_xxx', 'changed_mind')\nif (!cancelled.ok) {\n // Codes you'll see: ORDER_NOT_CANCELLABLE, ORDER_ALREADY_FULFILLED\n console.error(cancelled.error.code, cancelled.error.message)\n}\n\n// Idempotent retry: same key, same outcome on the server\nawait client.orders.cancel('ord_xxx', 'changed_mind', {\n idempotencyKey: 'cancel-ord_xxx-once',\n})\n```\n\n \n\n \n\n## Order status values\n\n \n \n\n| Status | Meaning |\n| --- | --- |\n| `pending` | Created, awaiting payment confirmation |\n| `confirmed` | Accepted by the business |\n| `preparing` | Being assembled or fulfilled |\n| `ready` | Ready for pickup or delivery dispatch |\n| `completed` | Fulfilled; terminal state |\n| `cancelled` | Cancelled; terminal state |\n| `refunded` | Payment refunded |\n\n \n\n \n\n## Poll until terminal\n\n \n \n\n```ts\nasync function pollUntilTerminal(orderId: string, intervalMs = 5000) {\n for (;;) {\n const r = await client.orders.get(orderId)\n if (!r.ok) return r\n\n const status = r.value.status\n if (status === 'completed' || status === 'cancelled' || status === 'refunded') {\n return r\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs))\n }\n}\n```\n\n \n\n \n\n## Method reference\n\n \n \n\n| Method | Returns |\n| --- | --- |\n| `list({ status?, limit?, offset? })` | `Result<Order[]>` |\n| `get(orderId)` | `Result<Order>` |\n| `cancel(orderId, reason?, opts?)` | `Result<Order>` |\n| `getRecent(limit?)` | `Result<Order[]>` |\n| `getByStatus(status)` | `Result<Order[]>` |\n\n \n\n \n\n## Related\n\n \n \n- [**Checkout**](/docs/sdk/checkout)\n Create the orders that this page lists\n\n \n- [**Subscriptions**](/docs/sdk/subscriptions)\n For recurring orders, see the subscription surface\n\n \n- [**Scheduling**](/docs/sdk/scheduling)\n Reschedule or cancel a booking line item\n\n \n- [**Error handling**](/docs/sdk/errors)\n Cancellation guards and retryable errors\n"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "subscriptions",
|
|
94
|
+
"title": "Subscriptions",
|
|
95
|
+
"description": "Recurring billing, plan changes, cancellation",
|
|
96
|
+
"source_url": "https://cimplify.dev/docs/sdk/subscriptions",
|
|
97
|
+
"body": "## List subscriptions\n\n \n \n\n```ts\nconst r = await client.subscriptions.list()\nif (!r.ok) {\n console.error(r.error.code, r.error.message)\n return\n}\n\nfor (const sub of r.value) {\n console.log(sub.id, sub.status, sub.next_billing_at)\n}\n```\n\n \n\n \n\n## Get a single subscription\n\n \n \n\n```ts\nconst r = await client.subscriptions.get('sub_xxx')\nif (!r.ok) return\n\nconst sub = r.value // SubscriptionWithDetails\nconsole.log(sub.plan)\nconsole.log(sub.items)\nconsole.log(sub.recent_invoices)\n```\n\n \n\n \n\n## Pause / resume\n\n \n\nPausing freezes future renewals indefinitely; resuming reactivates without re-running checkout.\n\n \n \n\n```ts\nawait client.subscriptions.pause('sub_xxx')\n// ...later\nawait client.subscriptions.resume('sub_xxx')\n```\n\n \n\n \n\n## Skip the next renewal\n\n \n \n\n```ts\nconst r = await client.subscriptions.skipNextRenewal('sub_xxx', {\n idempotencyKey: 'skip-sub_xxx-2026-05', // optional\n})\n\nif (!r.ok) {\n console.error(r.error.code, r.error.message)\n return\n}\n\nconsole.log(r.value.success)\n```\n\n \n\n \n\n## Cancel\n\n \n\nCancellation is terminal; the subscription cannot be resumed. Pass a reason for analytics.\n\n \n \n\n```ts\nconst r = await client.subscriptions.cancel('sub_xxx', 'too_expensive', {\n idempotencyKey: 'cancel-sub_xxx-once', // optional\n})\n\nif (!r.ok) {\n // Codes you'll see: SUBSCRIPTION_NOT_FOUND, ALREADY_CANCELLED\n console.error(r.error.code, r.error.message)\n}\n```\n\n \n\n \n\n## Method reference\n\n \n \n\n| Method | Returns |\n| --- | --- |\n| `list()` | `Result<Subscription[]>` |\n| `get(id)` | `Result<SubscriptionWithDetails>` |\n| `pause(id)` | `Result<{ success: boolean }>` |\n| `resume(id)` | `Result<{ success: boolean }>` |\n| `skipNextRenewal(id, opts?)` | `Result<{ success: boolean }>` |\n| `cancel(id, reason?, opts?)` | `Result<{ success: boolean }>` |\n\n \n\n \n\n## Related\n\n \n \n- [**Checkout**](/docs/sdk/checkout)\n Subscriptions are created at checkout time\n\n \n- [**Orders**](/docs/sdk/orders)\n Each renewal becomes a new order\n\n \n- [**Auth**](/docs/sdk/auth)\n Subscriptions require an authenticated customer\n\n \n- [**Error handling**](/docs/sdk/errors)\n ALREADY_CANCELLED, SUBSCRIPTION_NOT_FOUND\n"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "services",
|
|
101
|
+
"title": "Scheduling \u2014 List services",
|
|
102
|
+
"description": "Bookable services + variant-aware availability",
|
|
103
|
+
"source_url": "https://cimplify.dev/docs/sdk/scheduling#list-services",
|
|
104
|
+
"body": "## List services\n```ts\nconst r = await client.scheduling.getServices()\nif (!r.ok) {\n console.error(r.error.code, r.error.message)\n return\n}\n\nfor (const service of r.value) {\n console.log(service.id, service.name, service.duration_minutes)\n}\n\nconst single = await client.scheduling.getService('svc_xxx')\n```\n"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "bookings",
|
|
108
|
+
"title": "Scheduling \u2014 Get available slots (variant-aware)",
|
|
109
|
+
"description": "Picking slots, creating bookings, reschedule and cancel",
|
|
110
|
+
"source_url": "https://cimplify.dev/docs/sdk/scheduling#get-available-slots-variant-aware",
|
|
111
|
+
"body": "## Get available slots (variant-aware)\nPass `variant_id` when the service has variants; slot times and capacities depend on which variant the customer is booking.\n\n \n \n\n```ts\nconst r = await client.scheduling.getAvailableSlots({\n service_id: 'svc_haircut',\n date: '2026-05-08',\n variant_id: 'var_haircut_premium', // 60-minute variant\n participant_count: 1,\n})\n\nif (!r.ok) return\n\nfor (const slot of r.value) {\n console.log(slot.start_time, slot.end_time, slot.capacity_available)\n console.log(slot.available_staff)\n}\n```\n"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "errors",
|
|
115
|
+
"title": "Errors",
|
|
116
|
+
"description": "CimplifyError codes and remediation",
|
|
117
|
+
"source_url": "https://cimplify.dev/docs/sdk/errors",
|
|
118
|
+
"body": "## Result type\n\n \n \n\n```ts\ntype Result<T, E = CimplifyError> =\n | { ok: true; value: T }\n | { ok: false; error: E }\n```\n\n \n\n \n\n## CimplifyError\n\n \n \n\n```ts\n\nclass CimplifyError {\n code: string // 'VALIDATION_ERROR', 'NOT_FOUND', 'RATE_LIMITED', ...\n message: string // human-readable\n retryable?: boolean // safe to retry?\n context?: Record<string, unknown> // structured details (field name, validation rule, ...)\n}\n```\n\n \n\n \n\n## Read errors\n\n \n \n\n```ts\nconst r = await client.catalogue.getProducts()\n\nif (!r.ok) {\n console.error(r.error.code) // machine-readable\n console.error(r.error.message) // human-readable\n console.error(r.error.retryable) // boolean\n console.error(r.error.context) // optional structured detail\n return\n}\n\nconsole.log(r.value.items)\n```\n\n \n\n \n\n## Branch on code\n\n \n \n\n```ts\nconst r = await client.cart.addItem({ item_id: 'prod_xxx', quantity: 1 })\n\nif (!r.ok) {\n switch (r.error.code) {\n case 'OUT_OF_STOCK':\n showToast('This item is out of stock')\n break\n case 'ITEM_UNAVAILABLE':\n showToast('No longer available')\n break\n case 'VALIDATION_ERROR':\n showToast(r.error.message)\n break\n case 'RATE_LIMITED':\n showToast('Slow down; try again in a moment')\n break\n default:\n showToast(r.error.message)\n }\n}\n```\n\n \n\n \n\n## Retry helper\n\n \n\nOnly retry when the server has marked the error `retryable`. Use exponential backoff and a small attempt cap.\n\n \n \n\n```ts\n\nasync function withRetry<T>(\n fn: () => Promise<Result<T>>,\n maxAttempts = 3,\n): Promise<Result<T>> {\n let last!: Result<T>\n for (let i = 0; i < maxAttempts; i++) {\n last = await fn()\n if (last.ok) return last\n if (!last.error.retryable) return last\n await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** i))\n }\n return last\n}\n\nconst r = await withRetry(() => client.catalogue.getProducts({ limit: 24 }))\n```\n\n \n\n \n\n## Common error codes\n\n \n \n\n| Code | Retryable | Meaning |\n| --- | --- | --- |\n| `VALIDATION_ERROR` | No | Bad input; fix the request |\n| `NOT_FOUND` | No | Resource missing or filtered out |\n| `UNAUTHORIZED` | No | Sign in / refresh the session |\n| `FORBIDDEN` | No | Authenticated but not allowed |\n| `RATE_LIMITED` | Yes | Back off and retry |\n| `NETWORK_ERROR` | Yes | Transport failed before response |\n| `TIMEOUT` | Yes | Server did not respond in time |\n| `SERVER_ERROR` | Yes | Generic 5xx |\n\n \n\n \n\n## Checkout-specific codes\n\n \n \n\n| Code | Recoverable | Meaning |\n| --- | --- | --- |\n| `INVALID_CART` | No | Cart is empty or no longer valid |\n| `ALREADY_PROCESSING` | No | A checkout for this cart is already in flight |\n| `PAYMENT_FAILED` | Yes | Provider declined; let the user retry |\n| `FX_QUOTE_FAILED` | Yes | Could not lock an FX quote for the pay currency |\n| `AUTH_INCOMPLETE` | Yes | Customer must finish OTP / PIN authorization |\n| `AUTH_LOST` | Yes | Session expired mid-checkout |\n| `CHECKOUT_FAILED` | Sometimes | Generic terminal failure; read message + retryable |\n\n \n\n \n\n## Schema-shape errors (testing only)\n\n \n\nInside the testing harness, `assertX` helpers throw `SchemaViolationError` with structured `issues[]`. This is the only place the SDK throws.\n\n \n \n\n```ts\n\ntry {\n assertCart(response)\n} catch (e) {\n if (e instanceof SchemaViolationError) {\n console.error(e.toJSON()) // RFC 7807 + structured issues[]\n }\n}\n```\n\n \n\n \n\n## Related\n\n \n \n- [**Result<T, E>**](/docs/concepts/result)\n Why methods never throw, how to narrow\n\n \n- [**Idempotency**](/docs/concepts/idempotency)\n Safe retries for write methods\n\n \n- [**Checkout**](/docs/sdk/checkout)\n Where most error codes show up\n\n \n- [**Auth**](/docs/sdk/auth)\n UNAUTHORIZED + AUTH_LOST recovery\n"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "uploads",
|
|
122
|
+
"title": "Uploads",
|
|
123
|
+
"description": "Presigned upload flow: init \u2192 PUT \u2192 confirm",
|
|
124
|
+
"source_url": "https://cimplify.dev/docs/sdk/uploads",
|
|
125
|
+
"body": "## Upload a File (sugar)\n\n \n \n\n```ts\nasync function handleFile(file: File) {\n const r = await client.uploads.upload(file)\n if (!r.ok) {\n console.error(r.error.code, r.error.message)\n return\n }\n\n console.log(r.value.id, r.value.url)\n console.log(r.value.filename, r.value.content_type, r.value.size_bytes)\n}\n```\n\n \n\n \n\n## Step-by-step (when you need control)\n\n \n\nUse the explicit form when you want to show progress, retry the PUT independently, or upload bytes from a non-File source (Blob, stream, etc.).\n\n \n \n\n```ts\n// 1. Init: get a presigned URL\nconst init = await client.uploads.init(file.name, file.type, file.size, {\n idempotencyKey: 'upload-' + file.name + '-' + file.size, // optional\n})\nif (!init.ok) {\n console.error(init.error.code, init.error.message)\n return\n}\n\nconst { upload_id, upload_url, expires_in_secs } = init.value\n\n// 2. PUT the bytes directly to storage\nconst put = await fetch(upload_url, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type },\n})\n\nif (!put.ok) {\n console.error('PUT failed', put.status)\n return\n}\n\n// 3. Confirm: server validates and indexes\nconst confirmed = await client.uploads.confirm(upload_id)\nif (!confirmed.ok) return\n\nconsole.log(confirmed.value.id, confirmed.value.url)\n```\n\n \n\n \n\n## React: a drop zone\n\n \n \n\n```tsx\n'use client'\n\nexport function DropZone({ onUploaded }: { onUploaded: (result: UploadResult) => void }) {\n const client = useCimplifyClient()\n const [status, setStatus] = useState<'idle' | 'uploading' | 'error'>('idle')\n\n async function pick(e: React.ChangeEvent<HTMLInputElement>) {\n const file = e.target.files?.[0]\n if (!file) return\n\n setStatus('uploading')\n const r = await client.uploads.upload(file)\n if (!r.ok) {\n setStatus('error')\n return\n }\n\n setStatus('idle')\n onUploaded(r.value)\n }\n\n return (\n <label>\n <input type=\"file\" onChange={pick} />\n <span>{status === 'uploading' ? 'Uploading\u2026' : 'Choose a file'}</span>\n </label>\n )\n}\n```\n\n \n\n \n\n## UploadResult shape\n\n \n \n\n| Field | Type | Notes |\n| --- | --- | --- |\n| `id` | `string` | Stable upload id |\n| `url` | `string` | Public, durable URL |\n| `filename` | `string` | Original filename |\n| `content_type` | `string` | MIME |\n| `size_bytes` | `number` | |\n\n \n\n \n\n## Method reference\n\n \n \n\n| Method | Returns |\n| --- | --- |\n| `init(filename, contentType, sizeBytes, opts?)` | `Result<UploadInitResponse>` |\n| `confirm(uploadId)` | `Result<UploadResult>` |\n| `upload(file)` | `Result<UploadResult>` |\n\n \n\n \n\n## Related\n\n \n \n- [**Support**](/docs/sdk/support)\n Attach uploads to chat messages\n\n \n- [**Auth**](/docs/sdk/auth)\n Profile photo via `updateProfile`\n\n \n- [**Error handling**](/docs/sdk/errors)\n UPLOAD_FAILED is retryable\n\n \n- [**Idempotency**](/docs/concepts/idempotency)\n Replay-safe upload init\n"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "assets",
|
|
129
|
+
"title": "Assets",
|
|
130
|
+
"description": "Storefront brand assets \u2014 upload to CDN, build URLs, render through next/image",
|
|
131
|
+
"source_url": "https://cimplify.dev/docs/sdk/assets",
|
|
132
|
+
"body": 'The asset surface on the SDK is **read-side only** \u2014 building URLs for images you\'ve already uploaded via [`cimplify assets upload`](/docs/cli/assets). Customer-runtime uploads still go through `client.uploads.upload(file)`.\n\n## `assetUrl(path, opts?)`\n\nPure URL builder. Prepends the CDN base for relative paths, passes absolute URLs through with the transform query contract appended.\n\n```ts\n\nassetUrl("hero/main.jpg")\n// "https://storefrontassetscdn.cimplify.io/hero/main.jpg"\n\nassetUrl("hero/main.jpg", { w: 1200, format: "webp", quality: 80 })\n// "https://storefrontassetscdn.cimplify.io/hero/main.jpg?w=1200&format=webp&quality=80"\n\nassetUrl("hero/main.jpg", { base: "http://localhost:8787/storage" })\n// "http://localhost:8787/storage/hero/main.jpg"\n\nassetUrl("https://res.cloudinary.com/foo/bar.jpg")\n// "https://res.cloudinary.com/foo/bar.jpg" (absolute \u2192 passthrough)\n```\n\n### Options\n\n| Field | Type | Notes |\n| --- | --- | --- |\n| `base` | `string` | Override the CDN base. Defaults to `DEFAULT_CDN_BASE_URL`. |\n| `w` / `h` | `number` | Width / height in pixels (Cloudflare Image Resizing) |\n| `quality` | `number` | 0\u2013100 |\n| `format` | `"auto" \\| "webp" \\| "avif" \\| "jpeg"` | `"auto"` lets the CDN pick by `Accept` header |\n\nThe transform query is only meaningful when the URL terminates on a CDN with Cloudflare Image Resizing enabled (the default Cimplify storefront CDN). For external hosts, unknown params are typically ignored \u2014 the image still loads at its original size.\n\n## `isCimplifyAsset(src, base?)`\n\nDecides whether `src` is hosted on a Cimplify CDN. Used by the storefront\'s `next/image` loader to skip transforms for external URLs.\n\n```ts\n\nisCimplifyAsset("hero/main.jpg") // true (relative \u2192 ours)\nisCimplifyAsset("/img/seed/bakery/x.jpg") // true (dev mock passthrough)\nisCimplifyAsset("https://storefrontassetscdn.cimplify.io/x.jpg") // true\nisCimplifyAsset("https://cdn.cimplify.io/x.jpg") // true\nisCimplifyAsset("https://static-tmp.cimplify.io/seed/x.jpg") // true\nisCimplifyAsset("https://res.cloudinary.com/demo/upload/x.jpg") // false\nisCimplifyAsset("https://images.unsplash.com/photo-xyz") // false\n```\n\n## Built-in `next/image` loader\n\nEvery storefront template scaffolded by `cimplify init` ships `lib/cimplify-loader.ts` and points `images.loaderFile` at it. The loader:\n\n```ts\n\nconst cdnBase = process.env.NEXT_PUBLIC_CIMPLIFY_CDN_URL?.trim() || undefined;\n\nconst cimplifyImageLoader: ImageLoader = ({ src, width, quality }) => {\n if (isCimplifyAsset(src, cdnBase)) {\n return assetUrl(src, { base: cdnBase, w: width, quality, format: "auto" });\n }\n return src;\n};\n\nexport default cimplifyImageLoader;\n```\n\n**Cloudinary URLs from `client.catalogue.getProducts()` flow through untouched.** Cimplify-hosted URLs get the transform query. The result: every `<Image>` works regardless of where its asset lives, and Cimplify-hosted ones get edge-optimized.\n\nTo point the loader at a different CDN (for testing, multi-CDN setups, etc.) set `NEXT_PUBLIC_CIMPLIFY_CDN_URL` in your `.env.local`:\n\n```\nNEXT_PUBLIC_CIMPLIFY_CDN_URL=http://localhost:8787/storage\n```\n\n## `useImage(path, opts?)` \u2014 React\n\nThin hook that returns `{ src, loader }` for `<Image>`:\n\n```tsx\n\nexport function Hero() {\n const { src, loader } = useImage("hero/main.jpg", { format: "webp", quality: 85 });\n return <Image src={src} loader={loader} alt="..." width={1200} height={800} />;\n}\n```\n\nIf `images.loaderFile` is already set globally in your `next.config.ts`, you typically don\'t need `useImage` \u2014 just `<Image src={assetUrl("hero/main.jpg")} />` works. `useImage` is useful when you want per-component transform overrides on a project that doesn\'t use a global loader.\n\n### Options\n\n| Field | Notes |\n| --- | --- |\n| `base` | Per-call CDN base override |\n| `w`, `h` | Default transform values; per-image `width` from `<Image>` still wins |\n| `format`, `quality` | Default transform values |\n\n`useImage` is `useMemo`-stable across renders when the path + opts don\'t change.\n\n## Constants\n\n```ts\n\nDEFAULT_CDN_BASE_URL // "https://storefrontassetscdn.cimplify.io"\nCIMPLIFY_CDN_HOSTS // readonly ["storefrontassetscdn.cimplify.io", "cdn.cimplify.io", "static-tmp.cimplify.io"]\n```\n\n## What\'s not in v1\n\n- **`useUpload` hook** \u2014 runtime customer-facing uploads with progress + cancel. Deferred to v1.1. Customer uploads today go through `client.uploads.upload(file)` (no progress, no cancel \u2014 just `await`).\n- **`SEED_IDS` typed const tree** \u2014 demo-only convenience for referencing seed-image slugs in code. Generate-from-seed-source step deferred to v1.1; today demos use `seedImage(industry, slug)` from the SDK\'s mock module.\n- **Cloudinary / S3 path-transform support** \u2014 the loader passes external hosts through unchanged; Cloudinary\'s `c_fill,w_500/` path transforms aren\'t synthesized.\n- **`assets rm` server-side delete** \u2014 CLI removes manifest entries; the remote blob stays until v1.1\'s media-manager `DELETE` wiring.\n\n## Where it lives in the platform\n\n- Wire format: `POST /v1/businesses/{id}/assets/{init,confirm}` \u2014 see the management API reference\n- Backend service: `crate::uploads::service::UploadService::init_storefront_asset`\n- Storage backend: Cloudflare R2 bucket `cimplify-storefront-assets`, bound to `storefrontassetscdn.cimplify.io`\n- Storage key: `assets/{business_id}/{folder}/{filename}` \u2014 deterministic, idempotent, agent-predictable\n\n## Related\n\n- [`cimplify assets`](/docs/cli/assets) \u2014 CLI surface for upload/ls/rm\n- [Uploads (customer-runtime)](/docs/sdk/uploads) \u2014 `client.uploads.upload(file)` for private 7-day-presigned uploads\n'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "exit-codes",
|
|
136
|
+
"title": "CLI \u2014 Exit codes",
|
|
137
|
+
"description": "CLI exit codes (NOT_LINKED=4, TIMEOUT=12, \u2026)",
|
|
138
|
+
"source_url": "https://cimplify.dev/docs/cli#exit-codes",
|
|
139
|
+
"body": "## Exit codes\nDistinct per error class so callers can branch on `$?` without parsing output:\n\n| Code | Name | Meaning |\n| --- | --- | --- |\n| `0` | OK | Success. For `deploy`, deployment is `active`. |\n| `1` | FAILED | Generic failure (build failed, deploy errored). |\n| `2` | SUPERSEDED | A newer push raced this deploy; usually fine. |\n| `3` | ABORTED | User canceled (Ctrl-C) or `cimplify_cancel_deployment`. |\n| `4` | NOT_LINKED | `.cimplify/project.json` missing. Run `cimplify link <id>`. |\n| `7` | INTERACTIVE_REQUIRED | A prompt was needed but `--yes` not passed and stdin is non-TTY. |\n| `9` | NOT_FOUND | Resource (project, domain, deploy) does not exist. |\n| `10` | NETWORK_ERROR | Could not reach `api.cimplify.io`. |\n| `12` | TIMEOUT | Long-poll exceeded its window; the underlying job may still be running. |\n| `20` | NOT_LOGGED_IN | No saved token. Run `cimplify login` or pass `--api-key`. |\n| `21` | AUTH_FAILED | Token expired or revoked. Re-login. |\n| `30` | INVALID_INPUT | Argument/flag validation failed. |\n\nFull list in `@cimplify/cli/errors`.\n"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "agent-prompts",
|
|
143
|
+
"title": "Agent prompts",
|
|
144
|
+
"description": "Copy-paste prompts for common Cimplify workflows",
|
|
145
|
+
"source_url": "https://cimplify.dev/docs/agent-prompts",
|
|
146
|
+
"body": 'These prompts are deliberately terse. Each one names the canonical doc the agent should read first, the exact commands or [MCP tools](/docs/mcp) to call, and the exit condition. Replace `<angle-bracket>` placeholders before running.\n\nEvery prompt assumes the agent has either:\n\n- the [Cimplify CLI](/docs/cli) installed and `cimplify login` complete, **or**\n- an MCP connection to `https://api.cimplify.io/mcp` ([setup](/docs/mcp#connect))\n\n## Ship a new storefront end-to-end\n\n```text title="Scaffold \u2192 deploy \u2192 custom domain"\nSet up a Cimplify <retail> storefront called <my-store> and deploy it to <shop.example.com>.\n\nSource of truth: https://cimplify.dev/docs/tldr\n- Use the MCP tools at api.cimplify.io/mcp when available; otherwise shell `cimplify <cmd> --json`.\n- After every command, echo the JSON envelope.\n- Stop on the first non-zero exit code and name it from https://cimplify.dev/docs/cli#exit-codes.\n- Poll deployment status with cimplify_get_deployment_status (or `cimplify status --json`); never `sleep`.\n- Idempotent \u2014 re-running any step is safe.\n```\n\n## Rebrand an existing storefront\n\n```text title="Apply a new brand to a scaffolded template"\nRebrand the storefront in <./my-store> to match these tokens:\n name: <Acme Coffee>\n primary: <#0F172A>\n accent: <#F59E0B>\n fontDisplay: <"Playfair Display">\n fontBody: <"Inter">\n\nRules (https://cimplify.dev/docs/templates/brand):\n- Single source of truth is `lib/brand.ts`. Update it first.\n- Mirror color tokens into `app/globals.css` `@theme` block. Do not hard-code colors elsewhere.\n- Do not edit ejected SDK components; they pick up tokens automatically.\n- Run `bun run check:brand` after edits; fix any schema errors it reports.\n- Verify by opening `http://localhost:3000` (start `bun dev` if not running).\n- Commit with message `brand: <name>`. Do not deploy.\n```\n\n## Add a homepage section\n\n```text title="New Server Component section, cache-tagged"\nAdd a `<Stories>` section to the homepage of the storefront in <./my-store>.\n\nPattern (mirrors the Server Component sections under `app/_sections/` in the scaffolded template):\n- Create `app/_sections/stories.tsx` as an async Server Component with `"use cache"` and `cacheTag(tags.collections())` from `@cimplify/sdk/server`.\n- Source data from `client.catalogue.getCollection({ slug: "stories" })`.\n- Render with the existing `<SectionHeader>` and `<ProductCard>` SDK components (do not eject).\n- Insert into `app/page.tsx` between the hero and the featured collection.\n- Run `bun run check` \u2014 must pass typecheck, lint, brand, cart-flow, contract.\n- Do not modify `lib/brand.ts` or any file under `components/ui/`.\n```\n\n## Migrate to a new SDK version\n\n```text title="SDK upgrade with breaking-change scan"\nUpgrade the storefront in <./my-store> from `@cimplify/sdk@<0.44.x>` to `@cimplify/sdk@<latest>`.\n\nProcess:\n- Read the release notes for the target range (`npm view @cimplify/sdk versions --json` to list, then `npm view @cimplify/sdk@<version>` per release).\n- Bump the pin in `package.json`, run `bun install`.\n- For each breaking change called out in the changelog, grep the project for the old symbol and apply the documented replacement.\n- Run `bun run check`. If `check:contract` fails, the SDK \u2194 mock contract drifted \u2014 stop and report.\n- Do not run `cimplify deploy`. End by printing a summary of every file you changed and every breaking change you applied.\n```\n\n## Add a custom checkout payment method\n\n```text title="Wire an additional payment option"\nAdd <mobile_money> as a checkout payment option in the storefront at <./my-store>.\n\nReference: https://cimplify.dev/docs/api-reference/checkout (flat ProcessArgs body \u2014 top-level fields, not nested).\n- The body shape is the contract \u2014 never wrap fields.\n- Eject the existing `<PaymentMethodPicker>` component with `cimplify add payment-method-picker`.\n- Add the option in the ejected picker, wiring the provider-specific body (e.g. `mobile_money_details: { phone_number, provider }`).\n- Validate the body against the typed `ProcessArgs` from `@cimplify/sdk`.\n- Run `bun run check:cart` \u2014 the add\u2192checkout flow must still pass.\n```\n\n## Provision a CI deploy key\n\n```text title="One-time setup for headless deploys"\nCreate a CI-scoped API key for the Cimplify project linked at <./my-store>, and write the GitHub Actions snippet that uses it.\n\nSteps:\n- Confirm `.cimplify/project.json` exists; if not, fail with NOT_LINKED (exit 4).\n- Print the dashboard URL the human needs to visit to create the key (https://app.cimplify.io/settings/developer).\n- Once the user provides `dk_live_\u2026`, store it as a repo secret named `CIMPLIFY_API_KEY` (instruct, do not perform).\n- Write `.github/workflows/deploy.yml` that runs on push to main: install CLI via the curl one-liner, `cimplify deploy --prod --api-key "$CIMPLIFY_API_KEY" --yes --json`.\n- Print the exit-code legend from https://cimplify.dev/docs/cli#exit-codes so the workflow can map failures.\n```\n\n## Diagnose a deploy that didn\'t go live\n\n```text title="Triage a failed or stuck deployment"\nThe latest deploy of the project at <./my-store> isn\'t live. Find out why.\n\nTriage path:\n- `cimplify status --json` \u2014 what\'s the latest deployment_id and status?\n- If status is `failed`, `cimplify logs --deployment <id> --json | tail -200`. Quote the first error line you see.\n- If status is `queued` or `building` for >5 minutes, the build is stuck \u2014 try `cimplify rollback <previous-deploy-id>`.\n- If status is `superseded` (exit 2), a newer push raced it; usually fine \u2014 confirm with `cimplify status` again.\n- If domains misroute, `cimplify domains ls --json` \u2014 confirm the verified+attached domain.\n- Report: deployment_id, status, the failing log line (if any), and the recommended next action. Do not auto-rollback without confirming.\n```\n\n## Add a hero image and reference it in the homepage\n\n```text title="Upload brand assets \u2192 use them in source"\nAdd the image at <./Downloads/hero.jpg> as the homepage hero for the storefront in <./my-store>.\n\nReference: https://cimplify.dev/docs/cli/assets\n- `cp <./Downloads/hero.jpg> public/hero/main.jpg`\n- `cimplify assets upload public/hero/ --folder hero --json` \u2014 captures the manifest entry for `hero/main.jpg`.\n- Edit `app/page.tsx`: replace the hero `<Image>` `src` with `assetUrl("hero/main.jpg")` from `@cimplify/sdk`.\n- Do NOT touch existing `<Image>` calls that point at Cloudinary or other external hosts \u2014 the loader passes them through unchanged.\n- Run `bun run check`. Open `http://localhost:3000`; verify the hero image renders.\n- Commit `cimplify-assets.json` along with the source change so the manifest is reviewable.\n```\n\n## Reset a sandbox storefront to a clean state\n\n```text title="Wipe and re-seed for repeatable demos"\nReset the local mock state for the storefront in <./my-store>.\n\n- Stop any running `bun dev` process.\n- `cimplify mock reset` (clears the in-process mock backing store).\n- `cimplify mock seed <retail>` (re-seeds with the chosen industry fixture).\n- `bun dev` to restart both servers.\n- Verify: `curl localhost:8787/v1/catalogue/products | jq \'.data | length\'` returns the expected seed count (~24 for retail).\n- Do not touch the linked cloud project; this is local-only.\n```\n\n## How these are built\n\nEvery prompt above is structured the same way so you can adapt them:\n\n1. **Goal** \u2014 one sentence, with `<angle-bracket>` placeholders for the moving parts.\n2. **Source of truth** \u2014 the canonical doc URL the agent reads first.\n3. **Rules** \u2014 operational guardrails: which tools to call, what to echo, when to stop.\n4. **Exit condition** \u2014 what counts as success, what counts as a halt.\n\nIf you build a prompt that should live here, open a PR against `content/docs/agent-prompts.mdx`. Keep them under ~12 lines; if a workflow needs more, it belongs in a dedicated doc page and the prompt should link to it.\n'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "seeds",
|
|
150
|
+
"title": "seeds",
|
|
151
|
+
"description": "Available mock seeds and how to switch between them",
|
|
152
|
+
"source_url": "https://cimplify.dev/docs/cli/mock",
|
|
153
|
+
"body": '## Mock seeds\n\nEvery Cimplify storefront ships with a local Hono mock backed by one of\nthese seeds. Switch with `cimplify mock seed <name>` (or hand-edit the\n`--seed` flag in your `package.json` `scripts.dev`).\n\n| Seed | Notes |\n| --- | --- |\n| `auto` | (no description) |\n| `default` | Generic small business \u2014 minimal fixtures |\n| `empty` | No data \u2014 for blank-slate testing |\n| `fashion` | Apparel with multi-axis variants (size, color) |\n| `grocery` | High-SKU produce + perishables |\n| `pharmacy` | Wellspring Apothecary \u2014 health products with regulated metadata |\n| `reesa-storefront` | Reesa-specific demo storefront |\n| `restaurant` | Menu-driven with reservations |\n| `retail` | Electronics-focused storefront with variants and add-ons |\n| `services` | Bookable services (e.g. salon, spa) |\n\n### Finding fixture IDs\n\nEach seed is a TypeScript file at `packages/sdk/src/mock/seeds/<name>.ts`\nin the cimplify monorepo. Product slugs, categories, and variant axes\nare defined inline. Grep that file when you need a real slug for a\nhard-coded demo:\n\n```bash\ngrep "slug:" node_modules/@cimplify/sdk/dist/mock/seeds/<name>.* | head\n```\n\nA future `cimplify mock products ls --json` will surface these IDs\ndirectly without grep.\n'
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
var TOPICS_BY_NAME = Object.fromEntries(
|
|
157
|
+
TOPICS.map((t) => [t.name, t])
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// src/commands/explain.ts
|
|
161
|
+
var CLI_VERSION = package_default.version ?? "unknown";
|
|
162
|
+
var NAME_COL_WIDTH = 14;
|
|
163
|
+
function listTopics(topics = TOPICS) {
|
|
164
|
+
const out = [];
|
|
165
|
+
out.push(bold("cimplify explain"));
|
|
166
|
+
out.push(dim(`\u2014 canonical guidance, bundled in cli-v${CLI_VERSION}`));
|
|
167
|
+
out.push("");
|
|
168
|
+
for (const t of topics) {
|
|
169
|
+
out.push(` ${bold(t.name.padEnd(NAME_COL_WIDTH))}${t.description}`);
|
|
170
|
+
}
|
|
171
|
+
out.push("");
|
|
172
|
+
out.push(dim(`Run \`cimplify explain <topic>\` to print the guide.`));
|
|
173
|
+
out.push(dim(`Add --json for a machine-readable envelope.`));
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
function renderTopic(topic) {
|
|
177
|
+
const out = [];
|
|
178
|
+
out.push(bold(`cimplify explain ${topic.name}`));
|
|
179
|
+
out.push("");
|
|
180
|
+
out.push(topic.body.trimEnd());
|
|
181
|
+
out.push("");
|
|
182
|
+
out.push(dim(`Source: ${topic.source_url} (snapshot in cli-v${CLI_VERSION})`));
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
function getTopic(name) {
|
|
186
|
+
return TOPICS_BY_NAME[name] ?? null;
|
|
187
|
+
}
|
|
188
|
+
function knownTopicNames() {
|
|
189
|
+
return TOPICS.map((t) => t.name);
|
|
190
|
+
}
|
|
191
|
+
async function run(argv) {
|
|
192
|
+
const args = parseArgs(argv);
|
|
193
|
+
const name = args.positional[0];
|
|
194
|
+
if (!name) {
|
|
195
|
+
for (const line of listTopics()) info(line);
|
|
196
|
+
result({
|
|
197
|
+
topics: TOPICS.map((t) => ({
|
|
198
|
+
name: t.name,
|
|
199
|
+
description: t.description,
|
|
200
|
+
source_url: t.source_url
|
|
201
|
+
}))
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const topic = getTopic(name);
|
|
206
|
+
if (!topic) {
|
|
207
|
+
throw new CliError(
|
|
208
|
+
CLI_ERROR_CODE.NOT_FOUND,
|
|
209
|
+
`Unknown topic "${name}".`,
|
|
210
|
+
{ remediation: `Run \`cimplify explain\` to see all topics (${knownTopicNames().length} available).` }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
for (const line of renderTopic(topic)) info(line);
|
|
214
|
+
result({
|
|
215
|
+
topic: topic.name,
|
|
216
|
+
title: topic.title,
|
|
217
|
+
body: topic.body,
|
|
218
|
+
source_url: topic.source_url,
|
|
219
|
+
snapshot_version: CLI_VERSION
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export { run as default, getTopic, listTopics, renderTopic };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run as default, extractMockSeed, gatherIntrospection, renderIntrospection } from './chunk-5IAYN7AJ.mjs';
|
|
3
|
+
import './chunk-RRY3NEZZ.mjs';
|
|
4
|
+
import './chunk-YI7UMMM7.mjs';
|
|
5
|
+
import './chunk-YQVMG62Z.mjs';
|
|
6
|
+
import './chunk-C4M3DXKC.mjs';
|
|
7
|
+
import './chunk-LS2VTSMQ.mjs';
|
|
8
|
+
import './chunk-I3XQSSOT.mjs';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs } from './chunk-
|
|
3
|
-
import { ApiClient } from './chunk-
|
|
4
|
-
import { readAuth, writeProjectLink } from './chunk-
|
|
5
|
-
import { CliError, CLI_ERROR_CODE, success, info, dim, result } from './chunk-
|
|
2
|
+
import { parseArgs } from './chunk-C4M3DXKC.mjs';
|
|
3
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
4
|
+
import { readAuth, writeProjectLink } from './chunk-LS2VTSMQ.mjs';
|
|
5
|
+
import { CliError, CLI_ERROR_CODE, success, info, dim, result } from './chunk-I3XQSSOT.mjs';
|
|
6
6
|
|
|
7
7
|
// src/commands/link.ts
|
|
8
8
|
function projectEndpoint(businessId, projectId) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { REGISTRY_INDEX } from './chunk-
|
|
3
|
-
import { parseArgs, flagBool } from './chunk-
|
|
4
|
-
import { CliError, CLI_ERROR_CODE, info, bold, dim, green, result } from './chunk-
|
|
2
|
+
import { REGISTRY_INDEX } from './chunk-MHK4WVNF.mjs';
|
|
3
|
+
import { parseArgs, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
4
|
+
import { CliError, CLI_ERROR_CODE, info, bold, dim, green, result } from './chunk-I3XQSSOT.mjs';
|
|
5
5
|
|
|
6
6
|
// src/commands/list.ts
|
|
7
7
|
async function run(argv) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs, flagString, flagBool } from './chunk-
|
|
3
|
-
import { resolveBaseUrl, ApiClient } from './chunk-
|
|
4
|
-
import { writeAuth } from './chunk-
|
|
5
|
-
import { CliError, CLI_ERROR_CODE, success, result, isJsonMode, step, info, dim } from './chunk-
|
|
2
|
+
import { parseArgs, flagString, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
3
|
+
import { resolveBaseUrl, ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
4
|
+
import { writeAuth } from './chunk-LS2VTSMQ.mjs';
|
|
5
|
+
import { CliError, CLI_ERROR_CODE, success, result, isJsonMode, step, info, dim } from './chunk-I3XQSSOT.mjs';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import { randomBytes, createHash } from 'crypto';
|
|
8
8
|
import { createServer } from 'http';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { clearAuth } from './chunk-
|
|
3
|
-
import { success, info, result } from './chunk-
|
|
2
|
+
import { clearAuth } from './chunk-LS2VTSMQ.mjs';
|
|
3
|
+
import { success, info, result } from './chunk-I3XQSSOT.mjs';
|
|
4
4
|
|
|
5
5
|
// src/commands/logout.ts
|
|
6
6
|
async function run(_argv) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { TERMINAL_DEPLOYMENT_STATUSES } from './chunk-MXYUAJEW.mjs';
|
|
3
|
-
import { parseArgs, flagString, flagBool } from './chunk-
|
|
4
|
-
import { ApiClient } from './chunk-
|
|
5
|
-
import { readAuth, readProjectLink, readProjectState } from './chunk-
|
|
6
|
-
import { CliError, CLI_ERROR_CODE, isJsonMode, failure, result, red } from './chunk-
|
|
3
|
+
import { parseArgs, flagString, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
4
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
5
|
+
import { readAuth, readProjectLink, readProjectState } from './chunk-LS2VTSMQ.mjs';
|
|
6
|
+
import { CliError, CLI_ERROR_CODE, isJsonMode, failure, result, red } from './chunk-I3XQSSOT.mjs';
|
|
7
7
|
|
|
8
8
|
// src/commands/logs.ts
|
|
9
9
|
var FLAG_DEPLOYMENT = "deployment";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { REPO_PROVIDER } from './chunk-MXYUAJEW.mjs';
|
|
3
|
-
import { parseArgs, flagString, flagBool } from './chunk-
|
|
4
|
-
import { ApiClient } from './chunk-
|
|
5
|
-
import { readAuth } from './chunk-
|
|
6
|
-
import { CliError, CLI_ERROR_CODE, info, dim, result, bold, success } from './chunk-
|
|
3
|
+
import { parseArgs, flagString, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
4
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
5
|
+
import { readAuth } from './chunk-LS2VTSMQ.mjs';
|
|
6
|
+
import { CliError, CLI_ERROR_CODE, info, dim, result, bold, success } from './chunk-I3XQSSOT.mjs';
|
|
7
7
|
|
|
8
8
|
// src/commands/projects.ts
|
|
9
9
|
var SUB_LS = "ls";
|
|
@@ -22,16 +22,18 @@ function provisionRepoEndpoint(businessId, projectId) {
|
|
|
22
22
|
function connectRepoEndpoint(businessId, projectId) {
|
|
23
23
|
return `/v1/businesses/${encodeURIComponent(businessId)}/projects/${encodeURIComponent(projectId)}/repo/connect`;
|
|
24
24
|
}
|
|
25
|
-
function
|
|
25
|
+
function extractHost(url) {
|
|
26
26
|
try {
|
|
27
|
-
|
|
28
|
-
if (parsed.hostname === "git.freestyle.sh") return REPO_PROVIDER.FREESTYLE;
|
|
29
|
-
if (parsed.hostname === "github.com" || parsed.hostname.endsWith(".github.com")) {
|
|
30
|
-
return REPO_PROVIDER.GITHUB;
|
|
31
|
-
}
|
|
32
|
-
if (parsed.hostname.includes("gitea")) return REPO_PROVIDER.GITEA;
|
|
27
|
+
return new URL(url).hostname;
|
|
33
28
|
} catch {
|
|
29
|
+
return /^(?:[^@/]+@)?([^:/]+):/.exec(url)?.[1] ?? "";
|
|
34
30
|
}
|
|
31
|
+
}
|
|
32
|
+
function detectProviderFromUrl(url) {
|
|
33
|
+
const host = extractHost(url);
|
|
34
|
+
if (host === "git.freestyle.sh") return REPO_PROVIDER.FREESTYLE;
|
|
35
|
+
if (host === "github.com" || host.endsWith(".github.com")) return REPO_PROVIDER.GITHUB;
|
|
36
|
+
if (host.includes("gitea")) return REPO_PROVIDER.GITEA;
|
|
35
37
|
return REPO_PROVIDER.EXTERNAL;
|
|
36
38
|
}
|
|
37
39
|
async function run(argv) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run as default, fetchCloneToken } from './chunk-QGBXGDA5.mjs';
|
|
3
|
+
import './chunk-RZQTHTXX.mjs';
|
|
4
|
+
import './chunk-MXYUAJEW.mjs';
|
|
5
|
+
import './chunk-C4M3DXKC.mjs';
|
|
6
|
+
import './chunk-D7WMSGKK.mjs';
|
|
7
|
+
import './chunk-LS2VTSMQ.mjs';
|
|
8
|
+
import './chunk-I3XQSSOT.mjs';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { pollDeployment } from './chunk-
|
|
2
|
+
import { pollDeployment } from './chunk-MOZQODQS.mjs';
|
|
3
3
|
import { DEPLOYMENT_STATUS, DEPLOY_TRIGGER } from './chunk-MXYUAJEW.mjs';
|
|
4
|
-
import { parseArgs, flagBool } from './chunk-
|
|
5
|
-
import { ApiClient } from './chunk-
|
|
6
|
-
import { readAuth, readProjectLink, writeProjectState } from './chunk-
|
|
7
|
-
import { CliError, CLI_ERROR_CODE, step, success, info, dim, result, EXIT_CODE } from './chunk-
|
|
4
|
+
import { parseArgs, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
5
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
6
|
+
import { readAuth, readProjectLink, writeProjectState } from './chunk-LS2VTSMQ.mjs';
|
|
7
|
+
import { CliError, CLI_ERROR_CODE, step, success, info, dim, result, EXIT_CODE } from './chunk-I3XQSSOT.mjs';
|
|
8
8
|
|
|
9
9
|
// src/commands/rollback.ts
|
|
10
10
|
var FLAG_NO_POLL = "no-poll";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { DEPLOYMENT_STATUS } from './chunk-MXYUAJEW.mjs';
|
|
3
|
-
import { parseArgs } from './chunk-
|
|
4
|
-
import { ApiClient } from './chunk-
|
|
5
|
-
import { readAuth, readProjectLink, readProjectState } from './chunk-
|
|
6
|
-
import { bold, dim, info, result } from './chunk-
|
|
3
|
+
import { parseArgs } from './chunk-C4M3DXKC.mjs';
|
|
4
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
5
|
+
import { readAuth, readProjectLink, readProjectState } from './chunk-LS2VTSMQ.mjs';
|
|
6
|
+
import { bold, dim, info, result } from './chunk-I3XQSSOT.mjs';
|
|
7
7
|
|
|
8
8
|
// src/commands/status.ts
|
|
9
9
|
var QUERY_LIMIT = "limit";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { clearProjectLink } from './chunk-
|
|
3
|
-
import { success, info, result } from './chunk-
|
|
2
|
+
import { clearProjectLink } from './chunk-LS2VTSMQ.mjs';
|
|
3
|
+
import { success, info, result } from './chunk-I3XQSSOT.mjs';
|
|
4
4
|
|
|
5
5
|
// src/commands/unlink.ts
|
|
6
6
|
async function run(_argv) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { package_default } from './chunk-
|
|
3
|
-
import { promptYesNo } from './chunk-
|
|
4
|
-
import { parseArgs, flagBool, flagString } from './chunk-
|
|
5
|
-
import { success, bold, info, dim, result, failure, CliError, CLI_ERROR_CODE, step, isJsonMode } from './chunk-
|
|
2
|
+
import { package_default } from './chunk-YQVMG62Z.mjs';
|
|
3
|
+
import { promptYesNo } from './chunk-RZQTHTXX.mjs';
|
|
4
|
+
import { parseArgs, flagBool, flagString } from './chunk-C4M3DXKC.mjs';
|
|
5
|
+
import { success, bold, info, dim, result, failure, CliError, CLI_ERROR_CODE, step, isJsonMode } from './chunk-I3XQSSOT.mjs';
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
import { basename, dirname } from 'path';
|
|
8
8
|
|
|
@@ -52,8 +52,8 @@ function stripTagPrefix(tag) {
|
|
|
52
52
|
}
|
|
53
53
|
function compareSemver(a, b) {
|
|
54
54
|
const parse = (v) => {
|
|
55
|
-
const
|
|
56
|
-
return [
|
|
55
|
+
const parts = v.split(/[.+-]/);
|
|
56
|
+
return [Number(parts[0]) || 0, Number(parts[1]) || 0, Number(parts[2]) || 0];
|
|
57
57
|
};
|
|
58
58
|
const [a1, a2, a3] = parse(a);
|
|
59
59
|
const [b1, b2, b3] = parse(b);
|
|
@@ -193,4 +193,4 @@ async function run(argv) {
|
|
|
193
193
|
});
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
export { run as default };
|
|
196
|
+
export { compareSemver, run as default, stripTagPrefix };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ApiClient } from './chunk-
|
|
3
|
-
import { readAuth } from './chunk-
|
|
4
|
-
import { info, bold, dim, result } from './chunk-
|
|
2
|
+
import { ApiClient } from './chunk-D7WMSGKK.mjs';
|
|
3
|
+
import { readAuth } from './chunk-LS2VTSMQ.mjs';
|
|
4
|
+
import { info, bold, dim, result } from './chunk-I3XQSSOT.mjs';
|
|
5
5
|
|
|
6
6
|
// src/commands/whoami.ts
|
|
7
7
|
var ENDPOINT_AUTH_ME = "/v1/auth/me";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cimplify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Cimplify CLI — deploy, manage env vars, link projects, and scaffold storefronts",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cimplify",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"templates"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"bundle-assets": "bun scripts/bundle-assets.ts",
|
|
20
|
+
"bundle-assets": "bun scripts/bundle-assets.ts && bun scripts/bundle-docs.ts",
|
|
21
21
|
"prebuild": "bun run bundle-assets",
|
|
22
22
|
"build": "tsup",
|
|
23
23
|
"build:binary": "bun run bundle-assets && bun scripts/build-binaries.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"prepublishOnly": "bun scripts/sync-template-pins.ts && bun run build",
|
|
28
28
|
"dev": "bun run bundle-assets && tsup --watch",
|
|
29
29
|
"typecheck": "bun run bundle-assets && tsgo --noEmit -p tsconfig.json",
|
|
30
|
-
"clean": "rm -rf dist bin src/embedded-assets.ts",
|
|
30
|
+
"clean": "rm -rf dist bin src/embedded-assets.ts src/embedded-docs.ts",
|
|
31
31
|
"lint:ox": "oxlint --fix .",
|
|
32
32
|
"format": "oxfmt . --write",
|
|
33
33
|
"format:check": "oxfmt . --check",
|