@zalify/cli 0.9.1 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +549 -112
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11293,7 +11293,7 @@ function updateNotifier(options) {
11293
11293
 
11294
11294
  // src/cli.ts
11295
11295
  import { createRequire as createRequire2 } from "node:module";
11296
- import { dirname as dirname2, join as join7 } from "node:path";
11296
+ import { dirname as dirname2, join as join8 } from "node:path";
11297
11297
  import { fileURLToPath as fileURLToPath3 } from "node:url";
11298
11298
 
11299
11299
  // src/auth.ts
@@ -12064,9 +12064,9 @@ async function imagesGenerate(storeDir) {
12064
12064
  import { readFileSync as readFileSync6, existsSync as existsSync6 } from "node:fs";
12065
12065
  import { basename as basename3, join as join6, resolve as resolve5 } from "node:path";
12066
12066
 
12067
- // src/storefront.ts
12068
- import { spawnSync as spawnSync3 } from "node:child_process";
12069
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
12067
+ // src/site.ts
12068
+ import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
12069
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
12070
12070
  import { basename as basename2, join as join5, resolve as resolve4 } from "node:path";
12071
12071
 
12072
12072
  // src/theme.ts
@@ -12493,11 +12493,11 @@ function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
12493
12493
  return depsChanged;
12494
12494
  }
12495
12495
 
12496
- // src/storefront.ts
12496
+ // src/site.ts
12497
12497
  function apiError2(status, json, appUrl) {
12498
12498
  if (status === 402) {
12499
12499
  return new Error(`${json.error ?? "Upgrade required"}
12500
- ` + ` Storefront hosting is a paid feature — upgrade the workspace in ${appUrl}`);
12500
+ ` + ` Site hosting is a paid feature — upgrade the workspace in ${appUrl}`);
12501
12501
  }
12502
12502
  if (json.code === "NO_CONNECTION") {
12503
12503
  return new Error(`${json.error}
@@ -12508,40 +12508,44 @@ function apiError2(status, json, appUrl) {
12508
12508
  Pick a different directory/slug name.`);
12509
12509
  }
12510
12510
  if (status === 404) {
12511
- return new Error("The storefronts API is not deployed yet (404). Update app.zalify.com first.");
12511
+ return new Error("The sites API is not deployed yet (404). Update app.zalify.com first.");
12512
12512
  }
12513
12513
  if (status === 503) {
12514
- return new Error(json.error ?? "Storefront provisioning is not configured on the server.");
12514
+ return new Error(json.error ?? "Site provisioning is not configured on the server.");
12515
12515
  }
12516
- return new Error(`storefronts API ${status}: ${JSON.stringify(json)}`);
12516
+ return new Error(`sites API ${status}: ${JSON.stringify(json)}`);
12517
12517
  }
12518
- async function post(auth, path9, body) {
12519
- const res = await fetch(`${auth.appUrl}${path9}`, {
12520
- method: "POST",
12521
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.key}` },
12522
- body: JSON.stringify({ workspaceId: auth.workspaceId, ...body })
12523
- });
12518
+ async function request(auth, method, path9, body) {
12519
+ const attempt = async (p) => {
12520
+ const url = method === "GET" ? `${auth.appUrl}${p}${p.includes("?") ? "&" : "?"}workspaceId=${auth.workspaceId}` : `${auth.appUrl}${p}`;
12521
+ return fetch(url, {
12522
+ method,
12523
+ headers: {
12524
+ Authorization: `Bearer ${auth.key}`,
12525
+ ...method === "POST" ? { "Content-Type": "application/json" } : {}
12526
+ },
12527
+ ...method === "POST" ? { body: JSON.stringify({ workspaceId: auth.workspaceId, ...body }) } : {}
12528
+ });
12529
+ };
12530
+ let res = await attempt(path9);
12531
+ if (res.status === 404 && path9.startsWith("/api/sites")) {
12532
+ res = await attempt(path9.replace("/api/sites", "/api/storefronts"));
12533
+ }
12524
12534
  const json = await res.json().catch(() => ({}));
12525
12535
  if (!res.ok)
12526
12536
  throw apiError2(res.status, json, auth.appUrl);
12527
12537
  return json;
12528
12538
  }
12529
- async function get(auth, path9) {
12530
- const sep = path9.includes("?") ? "&" : "?";
12531
- const res = await fetch(`${auth.appUrl}${path9}${sep}workspaceId=${auth.workspaceId}`, {
12532
- headers: { Authorization: `Bearer ${auth.key}` }
12533
- });
12534
- const json = await res.json().catch(() => ({}));
12535
- if (!res.ok)
12536
- throw apiError2(res.status, json, auth.appUrl);
12537
- return json;
12539
+ async function listSites(auth) {
12540
+ const json = await request(auth, "GET", "/api/sites");
12541
+ return json.sites ?? json.storefronts ?? [];
12538
12542
  }
12539
12543
  var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
12540
12544
  async function pollUntilReady(auth, id) {
12541
12545
  let lastMessage = "";
12542
12546
  const deadline = Date.now() + 10 * 60000;
12543
12547
  while (Date.now() < deadline) {
12544
- const view = await get(auth, `/api/storefronts/${id}`);
12548
+ const view = await request(auth, "GET", `/api/sites/${id}`);
12545
12549
  const message = view.statusMessage ?? view.status;
12546
12550
  if (message !== lastMessage) {
12547
12551
  console.log(` … ${message}`);
@@ -12556,16 +12560,19 @@ async function pollUntilReady(auth, id) {
12556
12560
  }
12557
12561
  throw new Error("Provisioning timed out after 10 minutes — check the Temporal UI.");
12558
12562
  }
12559
- async function storefrontCreate(dir2, opts) {
12563
+ async function siteCreate(dir2, opts) {
12560
12564
  const auth = requireActive();
12561
- const slug = basename2(resolve4(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "storefront";
12562
- console.log(`Provisioning storefront "${slug}" (workspace "${auth.workspaceName}")`);
12563
- const { storefrontId } = await post(auth, "/api/storefronts", {
12565
+ const slug = basename2(resolve4(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "site";
12566
+ console.log(`Provisioning site "${slug}" (workspace "${auth.workspaceName}")`);
12567
+ const created = await request(auth, "POST", "/api/sites", {
12564
12568
  slug,
12565
12569
  theme: opts.theme ?? "nextjs",
12566
12570
  ...opts.to ? { themeVersion: opts.to } : {}
12567
12571
  });
12568
- const view = await pollUntilReady(auth, storefrontId);
12572
+ const siteId = created.siteId ?? created.storefrontId;
12573
+ if (!siteId)
12574
+ throw new Error("provisioning API returned no site id");
12575
+ const view = await pollUntilReady(auth, siteId);
12569
12576
  console.log(` ✓ provisioned: ${view.githubRepo} → https://${view.domain}`);
12570
12577
  await themeCreate(dir2, {
12571
12578
  template: view.theme,
@@ -12576,9 +12583,13 @@ async function storefrontCreate(dir2, opts) {
12576
12583
  install: opts.install,
12577
12584
  git: opts.git
12578
12585
  });
12586
+ writeFileSync6(join5(resolve4(dir2), ".zalify", "site.json"), JSON.stringify({ id: siteId, slug: view.slug, domain: view.domain, repo: view.githubRepo }, null, 2) + `
12587
+ `);
12579
12588
  if (opts.git !== false && view.githubRepo) {
12580
- const { token, repo } = await post(auth, `/api/storefronts/${storefrontId}/push-token`, {});
12589
+ const { token, repo } = await request(auth, "POST", `/api/sites/${siteId}/push-token`, {});
12581
12590
  const run = (args) => spawnSync3("git", args, { cwd: resolve4(dir2), stdio: "ignore" });
12591
+ run(["add", ".zalify/site.json"]);
12592
+ run(["commit", "-m", "Link site record", "--no-verify"]);
12582
12593
  run(["remote", "add", "origin", `https://github.com/${repo}.git`]);
12583
12594
  const push = spawnSync3("git", ["push", `https://x-access-token:${token}@github.com/${repo}.git`, "main"], { cwd: resolve4(dir2), stdio: "inherit" });
12584
12595
  if (push.status !== 0) {
@@ -12587,7 +12598,7 @@ async function storefrontCreate(dir2, opts) {
12587
12598
  run(["update-ref", "refs/remotes/origin/main", "HEAD"]);
12588
12599
  run(["config", "branch.main.remote", "origin"]);
12589
12600
  run(["config", "branch.main.merge", "refs/heads/main"]);
12590
- console.log(` ✓ pushed to github.com/${repo} — Vercel deploys on push`);
12601
+ console.log(` ✓ pushed to github.com/${repo} — deploys on push`);
12591
12602
  }
12592
12603
  if (view.domain) {
12593
12604
  console.log(` Waiting for https://${view.domain} …`);
@@ -12607,49 +12618,89 @@ Live: https://${view.domain}`);
12607
12618
  console.log(`
12608
12619
  Next steps:
12609
12620
  ` + ` - author your theme in ${dir2}/theme/ (see theme/README.md), commit + push
12610
- ` + ` - zalify shopify import stores/<slug> --prune # catalog (auto-flushes the site cache)
12611
- ` + ` - zalify theme status ${dir2} # drift check anytime`);
12621
+ ` + ` - zalify brand push <brand-dir> --prune # catalog (auto-refreshes this site)
12622
+ ` + ` - zalify site open ${slug} # see it live
12623
+ ` + ` - zalify theme status ${dir2} # drift check anytime`);
12624
+ }
12625
+ async function resolveSite(auth, slugOrDir) {
12626
+ const fromDir = (dir2) => {
12627
+ const link = join5(dir2, ".zalify", "site.json");
12628
+ if (existsSync5(link)) {
12629
+ const parsed = JSON.parse(readFileSync5(link, "utf8"));
12630
+ if (parsed.slug)
12631
+ return parsed.slug;
12632
+ }
12633
+ const storeJson = join5(dir2, "store.json");
12634
+ if (existsSync5(storeJson)) {
12635
+ const parsed = JSON.parse(readFileSync5(storeJson, "utf8"));
12636
+ if (parsed.slug)
12637
+ return parsed.slug;
12638
+ }
12639
+ return null;
12640
+ };
12641
+ let slug = null;
12642
+ if (slugOrDir && existsSync5(resolve4(slugOrDir)))
12643
+ slug = fromDir(resolve4(slugOrDir));
12644
+ if (!slug && slugOrDir)
12645
+ slug = slugOrDir;
12646
+ if (!slug)
12647
+ slug = fromDir(process.cwd());
12648
+ const sites = await listSites(auth);
12649
+ if (slug) {
12650
+ const match = sites.find((s) => s.slug === slug);
12651
+ if (match)
12652
+ return match;
12653
+ throw new Error(`No site "${slug}" in workspace "${auth.workspaceName}".
12654
+ ` + ` Sites here: ${sites.map((s) => s.slug).join(", ") || "(none)"}`);
12655
+ }
12656
+ if (sites.length === 1)
12657
+ return sites[0];
12658
+ throw new Error(sites.length === 0 ? `No sites in workspace "${auth.workspaceName}" — create one with \`zalify site create <dir>\`.` : `Multiple sites in this workspace — name one: ${sites.map((s) => s.slug).join(", ")}`);
12612
12659
  }
12613
- async function storefrontList() {
12660
+ async function siteList() {
12614
12661
  const auth = requireActive();
12615
- const { storefronts } = await get(auth, "/api/storefronts");
12616
- if (!storefronts.length) {
12617
- console.log(`No storefronts in workspace "${auth.workspaceName}".`);
12662
+ const sites = await listSites(auth);
12663
+ if (!sites.length) {
12664
+ console.log(`No sites in workspace "${auth.workspaceName}".`);
12618
12665
  return;
12619
12666
  }
12620
- for (const s of storefronts) {
12621
- const site = s.domain ? `https://${s.domain}` : "-";
12622
- console.log(`${s.status === "READY" ? "✓" : s.status === "FAILED" ? "✗" : "…"} ${s.slug} ${site} (${s.status.toLowerCase()}${s.githubRepo ? `, ${s.githubRepo}` : ""})`);
12667
+ for (const s of sites) {
12668
+ const url = s.domain ? `https://${s.domain}` : "-";
12669
+ console.log(`${s.status === "READY" ? "✓" : s.status === "FAILED" ? "✗" : "…"} ${s.slug} ${url} (${s.status.toLowerCase()}${s.githubRepo ? `, ${s.githubRepo}` : ""})`);
12623
12670
  }
12624
12671
  }
12625
- function resolveSlug(slugOrDir) {
12626
- const storeJson = join5(resolve4(slugOrDir), "store.json");
12627
- if (existsSync5(storeJson)) {
12628
- const parsed = JSON.parse(readFileSync5(storeJson, "utf8"));
12629
- if (parsed.slug)
12630
- return parsed.slug;
12631
- }
12632
- return basename2(slugOrDir);
12672
+ async function siteRefresh(slugOrDir) {
12673
+ const auth = requireActive();
12674
+ const site = await resolveSite(auth, slugOrDir);
12675
+ await request(auth, "POST", `/api/sites/${site.id}/revalidate`, {});
12676
+ console.log(`✓ refreshed https://${site.domain}`);
12633
12677
  }
12634
- async function storefrontRevalidate(slugOrDir) {
12678
+ async function siteOpen(slugOrDir) {
12635
12679
  const auth = requireActive();
12636
- const slug = resolveSlug(slugOrDir);
12637
- const { storefronts } = await get(auth, "/api/storefronts");
12638
- const match = storefronts.find((s) => s.slug === slug);
12639
- if (!match)
12640
- throw new Error(`No storefront "${slug}" in workspace "${auth.workspaceName}".`);
12641
- await post(auth, `/api/storefronts/${match.id}/revalidate`, {});
12642
- console.log(`✓ flushed cache on https://${match.domain}`);
12643
- }
12644
- async function maybeRevalidateStorefront(auth, storeDir) {
12680
+ const site = await resolveSite(auth, slugOrDir);
12681
+ if (!site.domain)
12682
+ throw new Error(`Site "${site.slug}" has no domain yet.`);
12683
+ const url = `https://${site.domain}`;
12684
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
12685
+ spawn3(opener, [url], { detached: true, stdio: "ignore" }).unref();
12686
+ console.log(url);
12687
+ }
12688
+ async function maybeRefreshSites(auth, brandDir, siteSlugOverride) {
12645
12689
  try {
12646
- const slug = resolveSlug(storeDir);
12647
- const { storefronts } = await get(auth, "/api/storefronts");
12648
- const match = storefronts.find((s) => s.slug === slug && s.status === "READY");
12649
- if (!match)
12690
+ let slug = siteSlugOverride ?? null;
12691
+ if (!slug) {
12692
+ const storeJson = join5(resolve4(brandDir), "store.json");
12693
+ if (existsSync5(storeJson)) {
12694
+ slug = JSON.parse(readFileSync5(storeJson, "utf8")).slug ?? null;
12695
+ }
12696
+ }
12697
+ if (!slug)
12650
12698
  return;
12651
- await post(auth, `/api/storefronts/${match.id}/revalidate`, {});
12652
- console.log(`✓ site cache flushed (https://${match.domain})`);
12699
+ const sites = (await listSites(auth)).filter((s) => s.slug === slug && s.status === "READY");
12700
+ for (const site of sites) {
12701
+ await request(auth, "POST", `/api/sites/${site.id}/revalidate`, {});
12702
+ console.log(`✓ site refreshed (https://${site.domain})`);
12703
+ }
12653
12704
  } catch {}
12654
12705
  }
12655
12706
 
@@ -12829,9 +12880,9 @@ async function shopifyImport(storeDir, options = {}) {
12829
12880
  console.warn(` ! could not publish to Online Store (write_publications scope missing?): ${e instanceof Error ? e.message : String(e)}`);
12830
12881
  }
12831
12882
  console.log("Done.");
12832
- await maybeRevalidateStorefront(auth, dir2);
12883
+ await maybeRefreshSites(auth, dir2, options.site);
12833
12884
  }
12834
- async function shopifyUploadImages(storeDir) {
12885
+ async function shopifyUploadImages(storeDir, options = {}) {
12835
12886
  const auth = requireActive();
12836
12887
  const dir2 = resolve5(storeDir);
12837
12888
  const manifest = readJson(join6(dir2, "images", "manifest.json"), "images/manifest.json");
@@ -12907,13 +12958,336 @@ async function shopifyUploadImages(storeDir) {
12907
12958
  console.log(` ✓ ${entry.file}`);
12908
12959
  }
12909
12960
  console.log("Done.");
12910
- await maybeRevalidateStorefront(auth, dir2);
12961
+ await maybeRefreshSites(auth, dir2, options.site);
12962
+ }
12963
+
12964
+ // src/brand.ts
12965
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
12966
+ import { basename as basename4, join as join7, resolve as resolve6 } from "node:path";
12967
+ var AUTHORING_MD = `# Brand authoring guide
12968
+
12969
+ The rules for filling in this folder. An author — human or AI agent —
12970
+ should be able to produce a complete, import-ready brand from this file
12971
+ alone. Validate anytime with \`zalify brand validate\` (defaults to cwd).
12972
+
12973
+ ## Files
12974
+
12975
+ | File | Role |
12976
+ | ---- | ---- |
12977
+ | store.json | the Shopify store this brand connects to (name, slug, vertical, currency; storeDomain filled in once the store exists) |
12978
+ | brand.md | brand voice guide — tone, audience, naming, price band. Written FIRST; everything else follows it |
12979
+ | catalog.json | canonical catalog (products + collections) — the single source the pipeline imports |
12980
+ | images/manifest.json | one entry per shot + shared style/modelStyle art-direction prefixes |
12981
+ | images/*.png | generated masters (NOT committed — they live in the Zalify asset library; images/assets.json is the committed index) |
12982
+
12983
+ ## catalog.json rules
12984
+
12985
+ - 10–14 products is the sweet spot for a demo brand; every product needs
12986
+ \`handle\` (unique, kebab-case, no SKU-speak), \`title\`, \`descriptionHtml\`
12987
+ (brand voice; simple HTML: p/ul/li/em only), \`type\`, \`tags\`, \`variants\`.
12988
+ - **Collections are smart collections driven by tags**: a product joins
12989
+ collection \`x\` by carrying tag \`collection:x\`, and the collection's
12990
+ \`rule\` is \`{ "tag": "collection:x" }\`. Every product belongs to at
12991
+ least one collection.
12992
+ - **Mandatory collections**: \`best-sellers\` and \`sale\`. Sale products
12993
+ carry a real \`compareAtPrice\` AND the \`collection:sale\` tag.
12994
+ - \`options\` is 0–3 option names; each variant's \`options\` array matches
12995
+ that length and order. Single-variant products omit \`options\` and give
12996
+ one variant with \`"options": []\`.
12997
+ - Prices are strings with 2 decimals, in the store currency. Honest
12998
+ markdowns only (15–25% off typical).
12999
+ - Colors and variants get evocative names per brand.md — never "Gray 02".
13000
+
13001
+ ## images/manifest.json rules
13002
+
13003
+ - Three shots per hero product: packshot (\`<handle>.png\`), macro detail
13004
+ (\`<handle>-detail.png\`), lifestyle/model (\`<handle>-model.png\`,
13005
+ \`"type": "model"\`). ~33–36 entries total. Every \`handle\` must exist in
13006
+ catalog.json; every entry needs \`alt\` text.
13007
+ - \`style\` prefixes packshot/detail prompts; \`modelStyle\` prefixes
13008
+ \`type: "model"\` prompts. Keep prompts concrete: materials, colors,
13009
+ camera angle, light.
13010
+ - **Prompt safety (gpt-image-2 rejects [sexual] otherwise)** — for
13011
+ human-model shots: say "woman"/"man", never "young …"; keep fitted
13012
+ garments layered or covered (blazer / cardigan / cover-up); crop at
13013
+ the chin or shoot from behind; prefer wider framing. Swimwear: show
13014
+ the garment via flat-lay + detail; make the model shot a cover-up
13015
+ scene. Pet/object products: no such constraints.
13016
+
13017
+ ## Pipeline (after authoring)
13018
+
13019
+ \`\`\`sh
13020
+ zalify brand validate # lint this folder (cwd)
13021
+ # create the Shopify dev store + Zalify workspace, then:
13022
+ zalify workspace set <workspace-slug>
13023
+ zalify site create <slug> # hosted site: repo + deploy + domain + token
13024
+ zalify brand push --prune # catalog → your Shopify store
13025
+ zalify brand images generate # create the manifest's shots
13026
+ zalify brand images attach # attach them to the products
13027
+ \`\`\`
13028
+ `;
13029
+ var BRAND_MD = (name) => `# ${name} — brand voice guide
13030
+
13031
+ ## Who we are
13032
+
13033
+ <2–3 sentences: what the brand makes, for whom, and the feeling it sells.>
13034
+
13035
+ ## Audience
13036
+
13037
+ <Who buys: age band, habits, what they care about. Name the customer the
13038
+ way the brand would ("pet parents", not "owners").>
13039
+
13040
+ ## Tone
13041
+
13042
+ - <3–5 bullets: adjectives with teeth, plus what the brand NEVER sounds like.>
13043
+ - <How to address the reader (second person? playful? expert-calm?).>
13044
+
13045
+ ## Naming conventions
13046
+
13047
+ - <Pattern for product names, with 2–3 examples.>
13048
+ - <How colors/variants are named (evocative, never "Gray 02").>
13049
+
13050
+ ## Price band
13051
+
13052
+ - <Floor–ceiling and what anchors each end. Markdown policy.>
13053
+
13054
+ ## Quick sniff test
13055
+
13056
+ <One sentence: how to tell a line of copy is (or isn't) this brand.>
13057
+ `;
13058
+ var STORE_JSON = (name, slug, vertical, currency) => JSON.stringify({
13059
+ name,
13060
+ slug,
13061
+ vertical,
13062
+ currency,
13063
+ storeDomain: null,
13064
+ notes: "Standalone-copy deployment — fill storeDomain when the Shopify dev store exists; no siteUrl on purpose."
13065
+ }, null, 2) + `
13066
+ `;
13067
+ var CATALOG_JSON = (vendor, currency) => JSON.stringify({
13068
+ vendor,
13069
+ currency,
13070
+ products: [
13071
+ {
13072
+ handle: "example-product",
13073
+ title: "The Example Product",
13074
+ descriptionHtml: "<p>Two short paragraphs in the brand voice: lead with the moment the customer uses it, then the practical facts.</p><ul><li>Material / composition</li><li>Care instructions</li><li>Size guidance</li></ul>",
13075
+ type: "Category",
13076
+ tags: ["example", "collection:best-sellers"],
13077
+ options: ["Color"],
13078
+ variants: [
13079
+ {
13080
+ options: ["Oat Cream"],
13081
+ sku: "EXM-001-OAT",
13082
+ price: "29.00",
13083
+ compareAtPrice: null,
13084
+ grams: 500,
13085
+ inventory: 50
13086
+ }
13087
+ ],
13088
+ seo: {
13089
+ title: "The Example Product — Brand",
13090
+ description: "One-sentence meta description in the brand voice."
13091
+ }
13092
+ }
13093
+ ],
13094
+ collections: [
13095
+ {
13096
+ handle: "best-sellers",
13097
+ title: "Best Sellers",
13098
+ descriptionHtml: "<p>The ones everyone comes back for.</p>",
13099
+ rule: { tag: "collection:best-sellers" }
13100
+ },
13101
+ {
13102
+ handle: "sale",
13103
+ title: "Sale",
13104
+ descriptionHtml: "<p>Real markdowns on favorites.</p>",
13105
+ rule: { tag: "collection:sale" }
13106
+ }
13107
+ ]
13108
+ }, null, 2) + `
13109
+ `;
13110
+ var MANIFEST_JSON = JSON.stringify({
13111
+ style: "<shared prefix for packshot/detail prompts: photography style, background, light, 'no text, no watermark. Square 1:1 composition.'>",
13112
+ modelStyle: "<shared prefix for type:'model' lifestyle prompts — scene, mood, framing. Mind the prompt-safety rules in AUTHORING.md for human models.>",
13113
+ images: [
13114
+ {
13115
+ handle: "example-product",
13116
+ file: "example-product.png",
13117
+ alt: "The Example Product in Oat Cream, front three-quarter view",
13118
+ prompt: "<concrete packshot prompt: the product, materials, colors, camera angle>"
13119
+ },
13120
+ {
13121
+ handle: "example-product",
13122
+ file: "example-product-detail.png",
13123
+ alt: "Macro detail of the Example Product's texture",
13124
+ prompt: "<extreme macro close-up prompt: texture, seam, hardware>"
13125
+ },
13126
+ {
13127
+ handle: "example-product",
13128
+ file: "example-product-model.png",
13129
+ type: "model",
13130
+ alt: "The Example Product in use",
13131
+ prompt: "<lifestyle prompt: who/what uses it, where, light>"
13132
+ }
13133
+ ]
13134
+ }, null, 2) + `
13135
+ `;
13136
+ function brandInit(dir2 = ".", opts = {}) {
13137
+ const target = resolve6(dir2);
13138
+ const slug = basename4(target).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
13139
+ if (existsSync7(target) && readdirSync3(target).length > 0) {
13140
+ throw new Error(`${dir2} already exists and is not empty`);
13141
+ }
13142
+ const name = opts.name ?? slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
13143
+ const currency = (opts.currency ?? "USD").toUpperCase();
13144
+ mkdirSync3(join7(target, "images"), { recursive: true });
13145
+ writeFileSync7(join7(target, "AUTHORING.md"), AUTHORING_MD);
13146
+ writeFileSync7(join7(target, "brand.md"), BRAND_MD(name));
13147
+ writeFileSync7(join7(target, "store.json"), STORE_JSON(name, slug, opts.vertical ?? "<vertical>", currency));
13148
+ writeFileSync7(join7(target, "catalog.json"), CATALOG_JSON(name, currency));
13149
+ writeFileSync7(join7(target, "images", "manifest.json"), MANIFEST_JSON);
13150
+ console.log(`Scaffolded brand data in ${dir2}:`);
13151
+ for (const f of ["AUTHORING.md", "brand.md", "store.json", "catalog.json", "images/manifest.json"]) {
13152
+ console.log(` + ${f}`);
13153
+ }
13154
+ console.log(`
13155
+ Author in this order: brand.md → catalog.json → images/manifest.json` + `
13156
+ (rules + worked examples in AUTHORING.md; lint with \`zalify brand validate ${dir2}\`)`);
13157
+ }
13158
+ function brandValidate(dir2 = ".") {
13159
+ const target = resolve6(dir2);
13160
+ const problems = [];
13161
+ const warnings = [];
13162
+ const readJson2 = (rel) => {
13163
+ const path9 = join7(target, rel);
13164
+ if (!existsSync7(path9)) {
13165
+ problems.push(`missing ${rel}`);
13166
+ return null;
13167
+ }
13168
+ try {
13169
+ return JSON.parse(readFileSync7(path9, "utf8"));
13170
+ } catch (err) {
13171
+ problems.push(`${rel}: invalid JSON — ${err instanceof Error ? err.message : err}`);
13172
+ return null;
13173
+ }
13174
+ };
13175
+ if (!existsSync7(join7(target, "brand.md")))
13176
+ problems.push("missing brand.md");
13177
+ const store = readJson2("store.json");
13178
+ if (store && (!store.name || !store.slug))
13179
+ problems.push("store.json: name and slug are required");
13180
+ const catalog = readJson2("catalog.json");
13181
+ const handles = new Set;
13182
+ if (catalog) {
13183
+ const products = catalog.products ?? [];
13184
+ if (!products.length)
13185
+ problems.push("catalog.json: no products");
13186
+ if (products.some((p) => p.handle === "example-product")) {
13187
+ warnings.push("catalog.json still contains the example-product stub");
13188
+ }
13189
+ const collectionTags = new Set((catalog.collections ?? []).map((c) => c.rule?.tag).filter(Boolean));
13190
+ for (const c of ["best-sellers", "sale"]) {
13191
+ if (!(catalog.collections ?? []).some((col) => col.handle === c)) {
13192
+ problems.push(`catalog.json: mandatory collection "${c}" missing`);
13193
+ }
13194
+ }
13195
+ for (const p of products) {
13196
+ const where = `product "${p.handle ?? p.title ?? "?"}"`;
13197
+ if (!p.handle || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(p.handle)) {
13198
+ problems.push(`${where}: handle must be kebab-case`);
13199
+ } else if (handles.has(p.handle)) {
13200
+ problems.push(`${where}: duplicate handle`);
13201
+ } else {
13202
+ handles.add(p.handle);
13203
+ }
13204
+ if (!p.title || !p.descriptionHtml || !p.type) {
13205
+ problems.push(`${where}: title, descriptionHtml, and type are required`);
13206
+ }
13207
+ const tags = p.tags ?? [];
13208
+ const colTags = tags.filter((t) => t.startsWith("collection:"));
13209
+ if (!colTags.length)
13210
+ problems.push(`${where}: no collection:<handle> tag`);
13211
+ for (const t of colTags) {
13212
+ if (!collectionTags.has(t)) {
13213
+ problems.push(`${where}: tag "${t}" matches no collection rule`);
13214
+ }
13215
+ }
13216
+ const optionCount = p.options?.length ?? 0;
13217
+ if (optionCount > 3)
13218
+ problems.push(`${where}: more than 3 options`);
13219
+ if (!p.variants?.length) {
13220
+ problems.push(`${where}: at least one variant required`);
13221
+ } else {
13222
+ const hasSale = tags.includes("collection:sale");
13223
+ let anyCompareAt = false;
13224
+ for (const v of p.variants) {
13225
+ if ((v.options?.length ?? 0) !== optionCount) {
13226
+ problems.push(`${where}: variant options length must match options (${optionCount})`);
13227
+ }
13228
+ if (!v.price || !/^\d+\.\d{2}$/.test(v.price)) {
13229
+ problems.push(`${where}: price must be a string with 2 decimals`);
13230
+ }
13231
+ if (v.compareAtPrice != null) {
13232
+ anyCompareAt = true;
13233
+ if (!/^\d+\.\d{2}$/.test(v.compareAtPrice)) {
13234
+ problems.push(`${where}: compareAtPrice must be a string with 2 decimals`);
13235
+ } else if (v.price && Number(v.compareAtPrice) <= Number(v.price)) {
13236
+ problems.push(`${where}: compareAtPrice must exceed price`);
13237
+ }
13238
+ }
13239
+ }
13240
+ if (hasSale && !anyCompareAt) {
13241
+ problems.push(`${where}: in collection:sale but no variant has compareAtPrice`);
13242
+ }
13243
+ if (anyCompareAt && !hasSale) {
13244
+ warnings.push(`${where}: has compareAtPrice but not tagged collection:sale`);
13245
+ }
13246
+ }
13247
+ }
13248
+ }
13249
+ const manifest = readJson2("images/manifest.json");
13250
+ if (manifest) {
13251
+ const images = manifest.images ?? [];
13252
+ if (!images.length)
13253
+ problems.push("images/manifest.json: no images");
13254
+ if (!manifest.style || manifest.style.startsWith("<")) {
13255
+ warnings.push("images/manifest.json: style prefix not filled in");
13256
+ }
13257
+ const files = new Set;
13258
+ for (const entry of images) {
13259
+ const where = `image "${entry.file ?? "?"}"`;
13260
+ if (!entry.file || !entry.prompt)
13261
+ problems.push(`${where}: file and prompt required`);
13262
+ if (!entry.alt)
13263
+ problems.push(`${where}: alt text required`);
13264
+ if (entry.file) {
13265
+ if (files.has(entry.file))
13266
+ problems.push(`${where}: duplicate file`);
13267
+ files.add(entry.file);
13268
+ }
13269
+ if (entry.handle && handles.size && !handles.has(entry.handle)) {
13270
+ problems.push(`${where}: handle "${entry.handle}" not in catalog.json`);
13271
+ }
13272
+ if (entry.type === "model" && (!manifest.modelStyle || manifest.modelStyle.startsWith("<"))) {
13273
+ warnings.push("images/manifest.json: modelStyle prefix not filled in (model shots present)");
13274
+ }
13275
+ }
13276
+ }
13277
+ for (const w of [...new Set(warnings)])
13278
+ console.log(` ! ${w}`);
13279
+ if (problems.length) {
13280
+ for (const p of problems)
13281
+ console.log(` ✗ ${p}`);
13282
+ throw new Error(`${problems.length} problem(s) — fix and re-run \`zalify brand validate\``);
13283
+ }
13284
+ console.log(`✓ valid: ${handles.size} product(s), ${catalog?.collections?.length ?? 0} collection(s), ${manifest?.images?.length ?? 0} image entries` + (warnings.length ? ` — ${warnings.length} warning(s)` : ""));
12911
13285
  }
12912
13286
 
12913
13287
  // src/cli.ts
12914
13288
  var __dirname4 = dirname2(fileURLToPath3(import.meta.url));
12915
13289
  var require2 = createRequire2(import.meta.url);
12916
- var pkg = require2(join7(__dirname4, "..", "package.json"));
13290
+ var pkg = require2(join8(__dirname4, "..", "package.json"));
12917
13291
  try {
12918
13292
  updateNotifier({ pkg }).notify({
12919
13293
  isGlobal: true,
@@ -12921,7 +13295,78 @@ try {
12921
13295
  });
12922
13296
  } catch {}
12923
13297
  var program2 = new Command;
12924
- program2.name("zalify").description("Zalify CLI - command-line interface for Zalify").version(pkg.version, "-v, --version", "output the current version");
13298
+ program2.name("zalify").description("Zalify CLI author a brand, launch its site, keep the theme current").version(pkg.version, "-v, --version", "output the current version");
13299
+ program2.addHelpText("after", `
13300
+ The golden path (new brand, start to live):
13301
+ 1. zalify brand init stores/acme author the data (no login needed)
13302
+ 2. zalify login && zalify workspace set <workspace>
13303
+ 3. zalify site create acme repo + hosting + domain + deploy
13304
+ 4. cd stores/acme && zalify brand push --prune
13305
+ 5. zalify brand images generate && zalify brand images attach
13306
+ 6. zalify theme status | upgrade stay current, keep your edits
13307
+ `);
13308
+ var moved = (to) => console.log(`(moved — this is now \`zalify ${to}\`)`);
13309
+ program2.command("login").description("Authenticate via the browser (one login covers all your workspaces)").option("--app-url <url>", "Zalify app origin (default: https://app.zalify.com)").action(async (options) => {
13310
+ await login(options);
13311
+ });
13312
+ program2.command("logout").description("Delete the stored CLI credentials").action(() => logout());
13313
+ program2.command("whoami").description("Verify the stored key and show workspace, plan, and key details").action(async () => {
13314
+ await whoami();
13315
+ });
13316
+ var workspace = program2.command("workspace").description("Active workspace — which Shopify store your commands target");
13317
+ workspace.command("list", { isDefault: true }).description("List authorized workspaces (* = active)").action(async () => {
13318
+ await workspaceList();
13319
+ });
13320
+ workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action(async (slugOrId) => {
13321
+ await workspaceSet(slugOrId);
13322
+ });
13323
+ var brand = program2.command("brand").description("Brand data — author the catalog, voice, and imagery; push it to your Shopify store");
13324
+ brand.command("init [dir]").description("Scaffold a brand folder (catalog, voice guide, image manifest + AUTHORING.md rules); no login needed").option("--name <name>", "brand name (default: derived from the directory)").option("--vertical <vertical>", "e.g. womens-fast-fashion, pet-supplies").option("--currency <code>", "ISO currency", "USD").action((dir2, options) => {
13325
+ brandInit(dir2, options);
13326
+ });
13327
+ brand.command("validate [dir]").description("Lint the brand folder against the authoring rules").action((dir2) => {
13328
+ brandValidate(dir2);
13329
+ });
13330
+ brand.command("push [dir]").description("Push catalog.json to your connected Shopify store (products, smart collections, publish; idempotent)").option("--prune", "delete store products (by catalog vendor) absent from catalog.json").option("--site <slug>", "refresh only this site afterwards (default: every site matching the brand)").action(async (dir2, options) => {
13331
+ await shopifyImport(dir2 ?? ".", options);
13332
+ });
13333
+ var brandImages = brand.command("images").description("The brand's imagery pipeline: generate → attach → push/pull masters");
13334
+ brandImages.command("generate [dir]").description("Generate the manifest's missing images on Zalify infrastructure (resumable)").action(async (dir2) => {
13335
+ await imagesGenerate(dir2 ?? ".");
13336
+ });
13337
+ brandImages.command("attach [dir]").description("Attach generated images to their Shopify products per the manifest (skips already-attached)").option("--site <slug>", "refresh only this site afterwards").action(async (dir2, options) => {
13338
+ await shopifyUploadImages(dir2 ?? ".", options);
13339
+ });
13340
+ brandImages.command("push [dir]").description("Upload image masters to the Zalify asset library (sha256 dedup, writes assets.json)").action(async (dir2) => {
13341
+ await assetsPush(dir2 ?? ".");
13342
+ });
13343
+ brandImages.command("pull [dir]").description("Download masters listed in images/assets.json (fresh-clone restore)").action(async (dir2) => {
13344
+ await assetsPull(dir2 ?? ".");
13345
+ });
13346
+ var site = program2.command("site").description("Hosted sites — Zalify provisions the repo, deployment, and domain");
13347
+ site.command("create <dir>").description("One-command launch: provision repo + deployment + domain + storefront token, scaffold locally, push, go live").option("-t, --theme <name>", "theme", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--to <version>", "theme version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init/push").action(async (dir2, options) => {
13348
+ await siteCreate(dir2, options);
13349
+ });
13350
+ site.command("list", { isDefault: true }).description("List the workspace's hosted sites").action(async () => {
13351
+ await siteList();
13352
+ });
13353
+ site.command("refresh [slug-or-dir]").description("Refresh the live site's data cache (defaults to the site linked to cwd)").action(async (slugOrDir) => {
13354
+ await siteRefresh(slugOrDir);
13355
+ });
13356
+ site.command("open [slug-or-dir]").description("Open the live site in your browser (defaults to the site linked to cwd)").action(async (slugOrDir) => {
13357
+ await siteOpen(slugOrDir);
13358
+ });
13359
+ var theme = program2.command("theme").description("Theme source code — scaffold a copy and upgrade it without losing your edits");
13360
+ theme.command("create <dir>").description("Scaffold only — no Zalify hosting (self-host path; `site create` is the fully-hosted launch)").option("-t, --template <name>", "liquid | hydrogen | nextjs", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--store-domain <domain>", "your-store.myshopify.com (default: mock.shop demo data)").option("--storefront-token <token>", "public Storefront API access token").option("--to <version>", "theme version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init + initial commit").option("--tarball <path>", "use a local @zalify/theme-templates .tgz instead of npm").action(async (dir2, options) => {
13361
+ await themeCreate(dir2, options);
13362
+ });
13363
+ theme.command("status [dir]").description("Which theme files have you edited? (checksums vs .zalify/theme.json)").action((dir2) => {
13364
+ themeStatus(dir2);
13365
+ });
13366
+ theme.command("upgrade [dir]").description("Move to a newer theme version: untouched files overwritten, edited files 3-way-merged, theme/ + .env never touched").option("--to <version>", "target theme version (default: latest)").option("--dry-run", "classify and report without writing anything").option("--install", "run install when dependencies changed").option("--tarball <path>", "target version as a local .tgz (testing)").option("--base-tarball <path>", "base version as a local .tgz (testing)").action(async (dir2, options) => {
13367
+ await themeUpgrade(dir2, options);
13368
+ });
13369
+ program2.command("self-update").description("Update the CLI to the latest version (auto-detects pnpm/bun/npm)").action(() => selfUpdate(pkg.version));
12925
13370
  program2.command("version").description("Print Zalify CLI version and check for updates").action(async () => {
12926
13371
  console.log(`Zalify CLI version: ${pkg.version}`);
12927
13372
  const isNewer = (a, b) => {
@@ -12938,62 +13383,54 @@ program2.command("version").description("Print Zalify CLI version and check for
12938
13383
  const update = await notifier.fetchInfo();
12939
13384
  if (update && isNewer(update.latest, update.current)) {
12940
13385
  console.log(`
12941
- A new version (${update.latest}) is available. Run "npm i -g @zalify/cli@latest" to update.`);
13386
+ A new version (${update.latest}) is available. Run "zalify self-update" to update.`);
12942
13387
  }
12943
13388
  } catch {}
12944
13389
  });
12945
- program2.command("self-update").description("Update the CLI to the latest version (auto-detects pnpm/bun/npm)").action(() => selfUpdate(pkg.version));
12946
- program2.command("login").description("Authenticate via the browser and store a workspace API key").option("--app-url <url>", "Zalify app origin (default: https://app.zalify.com)").action(async (options) => {
12947
- await login(options);
13390
+ var legacyStore = program2.command("store", { hidden: true });
13391
+ legacyStore.command("init <dir>").option("--name <name>").option("--vertical <vertical>").option("--currency <code>").action((dir2, options) => {
13392
+ moved("brand init");
13393
+ brandInit(dir2, options);
12948
13394
  });
12949
- program2.command("logout").description("Delete the stored CLI credentials").action(() => logout());
12950
- program2.command("whoami").description("Verify the stored key and show workspace, plan, and key details").action(async () => {
12951
- await whoami();
13395
+ legacyStore.command("validate <dir>").action((dir2) => {
13396
+ moved("brand validate");
13397
+ brandValidate(dir2);
12952
13398
  });
12953
- var workspace = program2.command("workspace").description("List authorized workspaces or switch the active one");
12954
- workspace.command("list", { isDefault: true }).description("List authorized workspaces (* = active)").action(async () => {
12955
- await workspaceList();
12956
- });
12957
- workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action(async (slugOrId) => {
12958
- await workspaceSet(slugOrId);
12959
- });
12960
- var images = program2.command("images").description("Generate images on Zalify infrastructure");
12961
- images.command("generate <store-dir>").description("Generate missing manifest images server-side (results land in the asset library and are downloaded locally)").action(async (storeDir) => {
12962
- await imagesGenerate(storeDir);
12963
- });
12964
- var shopify = program2.command("shopify").description("Push catalogs and images to the workspace's connected Shopify store");
12965
- shopify.command("import <store-dir>").description("Import <store-dir>/catalog.json into the connected Shopify store (products, smart collections, Online Store publish; idempotent by handle)").option("--prune", "delete store products (by catalog vendor) absent from catalog.json").action(async (storeDir, options) => {
13399
+ var legacyShopify = program2.command("shopify", { hidden: true });
13400
+ legacyShopify.command("import <store-dir>").option("--prune").action(async (storeDir, options) => {
13401
+ moved("brand push");
12966
13402
  await shopifyImport(storeDir, options);
12967
13403
  });
12968
- shopify.command("upload-images <store-dir>").description("Upload <store-dir>/images/* to matching products per images/manifest.json (skips files already attached)").action(async (storeDir) => {
13404
+ legacyShopify.command("upload-images <store-dir>").action(async (storeDir) => {
13405
+ moved("brand images attach");
12969
13406
  await shopifyUploadImages(storeDir);
12970
13407
  });
12971
- var storefront = program2.command("storefront").description("Provision and manage fully-hosted standalone storefronts (repo + Vercel via Zalify)");
12972
- storefront.command("create <dir>").description("One-command launch: Zalify provisions the GitHub repo + Vercel project (env, domain, storefront token), then scaffolds locally and pushes").option("-t, --theme <name>", "theme", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--to <version>", "template version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init/push").action(async (dir2, options) => {
12973
- await storefrontCreate(dir2, options);
12974
- });
12975
- storefront.command("list", { isDefault: true }).description("List the workspace's provisioned storefronts").action(async () => {
12976
- await storefrontList();
12977
- });
12978
- storefront.command("revalidate <slug-or-store-dir>").description("Flush the storefront site's data cache (secret stays server-side)").action(async (slugOrDir) => {
12979
- await storefrontRevalidate(slugOrDir);
13408
+ var legacyImages = program2.command("images", { hidden: true });
13409
+ legacyImages.command("generate <store-dir>").action(async (storeDir) => {
13410
+ moved("brand images generate");
13411
+ await imagesGenerate(storeDir);
12980
13412
  });
12981
- var theme = program2.command("theme").description("Scaffold and upgrade standalone Zalify storefronts (Shopify-theme model)");
12982
- theme.command("create <dir>").description("Scaffold a standalone storefront from the published theme templates").option("-t, --template <name>", "liquid | hydrogen | nextjs", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--store-domain <domain>", "your-store.myshopify.com (default: mock.shop demo data)").option("--storefront-token <token>", "public Storefront API access token").option("--to <version>", "template version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init + initial commit").option("--tarball <path>", "use a local @zalify/theme-templates .tgz instead of npm").action(async (dir2, options) => {
12983
- await themeCreate(dir2, options);
13413
+ var legacyAssets = program2.command("assets", { hidden: true });
13414
+ legacyAssets.command("push <store-dir>").action(async (storeDir) => {
13415
+ moved("brand images push");
13416
+ await assetsPush(storeDir);
12984
13417
  });
12985
- theme.command("status [dir]").description("List theme-owned files edited since scaffolding (.zalify/theme.json checksums)").action((dir2) => {
12986
- themeStatus(dir2);
13418
+ legacyAssets.command("pull <store-dir>").action(async (storeDir) => {
13419
+ moved("brand images pull");
13420
+ await assetsPull(storeDir);
12987
13421
  });
12988
- theme.command("upgrade [dir]").description("Upgrade to a newer theme version: untouched files overwritten, edited files 3-way-merged, theme/ + .env never touched").option("--to <version>", "target template version (default: latest)").option("--dry-run", "classify and report without writing anything").option("--install", "run install when dependencies changed").option("--tarball <path>", "target version as a local .tgz (testing)").option("--base-tarball <path>", "base version as a local .tgz (testing)").action(async (dir2, options) => {
12989
- await themeUpgrade(dir2, options);
13422
+ var legacyStorefront = program2.command("storefront", { hidden: true });
13423
+ legacyStorefront.command("create <dir>").option("-t, --theme <name>", "", "nextjs").option("--editor").option("--to <version>").option("--no-install").option("--no-git").action(async (dir2, options) => {
13424
+ moved("site create");
13425
+ await siteCreate(dir2, options);
12990
13426
  });
12991
- var assets = program2.command("assets").description("Sync images with the Zalify asset library");
12992
- assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
12993
- await assetsPush(storeDir);
13427
+ legacyStorefront.command("list", { isDefault: true }).action(async () => {
13428
+ moved("site list");
13429
+ await siteList();
12994
13430
  });
12995
- assets.command("pull <store-dir>").description("Download files listed in <store-dir>/images/assets.json").action(async (storeDir) => {
12996
- await assetsPull(storeDir);
13431
+ legacyStorefront.command("revalidate <slug-or-store-dir>").action(async (slugOrDir) => {
13432
+ moved("site refresh");
13433
+ await siteRefresh(slugOrDir);
12997
13434
  });
12998
13435
  program2.parseAsync().catch((err) => {
12999
13436
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",