@zalify/cli 0.24.1 → 0.26.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 (3) hide show
  1. package/README.md +8 -0
  2. package/dist/cli.js +135 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -58,6 +58,14 @@ API keys — list and revoke them in **Settings → Developer**. One login
58
58
  covers every workspace; `zalify workspace set` switches between them
59
59
  locally, no re-login.
60
60
 
61
+ **Inside a brand checkout, the folder decides.** `ads` commands read the
62
+ checkout's own workspace (its `.zalify/brand-repo.json` stamp, or
63
+ `identity.yaml` for folders AdsBuddy downloaded) and act as that
64
+ workspace — no `workspace set` first, and no risk of pulling one
65
+ brand's campaigns into another's folder. `workspace set` still matters
66
+ for commands run outside any checkout, and for `ads create`, which
67
+ provisions a NEW brand into the workspace you chose.
68
+
61
69
  ## Command reference
62
70
 
63
71
  ### Auth & workspace
package/dist/cli.js CHANGED
@@ -18259,7 +18259,7 @@ function updateNotifier(options) {
18259
18259
 
18260
18260
  // src/cli.ts
18261
18261
  import { createRequire as createRequire2 } from "node:module";
18262
- import { dirname as dirname3, join as join14 } from "node:path";
18262
+ import { dirname as dirname4, join as join14 } from "node:path";
18263
18263
  import { fileURLToPath as fileURLToPath3 } from "node:url";
18264
18264
 
18265
18265
  // src/auth.ts
@@ -18271,7 +18271,7 @@ import { spawn as spawn2 } from "node:child_process";
18271
18271
  // src/config.ts
18272
18272
  import { readFileSync, writeFileSync as writeFileSync2, mkdirSync, rmSync, existsSync } from "node:fs";
18273
18273
  import { homedir } from "node:os";
18274
- import { join } from "node:path";
18274
+ import { dirname, join, resolve } from "node:path";
18275
18275
  var dir = join(homedir(), ".zalify");
18276
18276
  var file = join(dir, "config.json");
18277
18277
  function migrate(raw) {
@@ -18316,12 +18316,83 @@ function writeConfig(config) {
18316
18316
  function deleteConfig() {
18317
18317
  rmSync(file, { force: true });
18318
18318
  }
18319
- function requireActive() {
18319
+ function enforceWorkspaceLink(root, auth) {
18320
+ const linkPath = join(root, ".zalify", "brand-repo.json");
18321
+ if (!existsSync(linkPath))
18322
+ return;
18323
+ let link;
18324
+ try {
18325
+ link = JSON.parse(readFileSync(linkPath, "utf8"));
18326
+ } catch {
18327
+ return;
18328
+ }
18329
+ const ws = link.workspace;
18330
+ if (!ws?.id) {
18331
+ link.workspace = {
18332
+ id: auth.workspaceId,
18333
+ name: auth.workspaceName,
18334
+ slug: auth.workspaceSlug
18335
+ };
18336
+ writeFileSync2(linkPath, `${JSON.stringify(link, null, 2)}
18337
+ `);
18338
+ console.log(`stamped workspace "${auth.workspaceSlug}" into .zalify/brand-repo.json — commit it.`);
18339
+ return;
18340
+ }
18341
+ if (ws.id !== auth.workspaceId) {
18342
+ throw new Error(`This checkout belongs to workspace "${ws.name ?? ws.slug ?? ws.id}" ` + `but the command is running as "${auth.workspaceName}" (${auth.workspaceSlug}).`);
18343
+ }
18344
+ }
18345
+ function checkoutWorkspace(startDir) {
18346
+ let cur = resolve(startDir ?? process.cwd());
18347
+ for (let up = 0;up < 12; up += 1) {
18348
+ const stamp = join(cur, ".zalify", "brand-repo.json");
18349
+ if (existsSync(stamp)) {
18350
+ try {
18351
+ const link = JSON.parse(readFileSync(stamp, "utf8"));
18352
+ const ws = link.workspace;
18353
+ if (ws?.id) {
18354
+ return {
18355
+ root: cur,
18356
+ id: ws.id,
18357
+ name: ws.name ?? ws.slug ?? ws.id,
18358
+ slug: ws.slug ?? ws.id
18359
+ };
18360
+ }
18361
+ } catch {}
18362
+ }
18363
+ const identity = join(cur, "identity.yaml");
18364
+ if (existsSync(identity)) {
18365
+ const text = readFileSync(identity, "utf8");
18366
+ const id = /^workspace_id:\s*(\S+)\s*$/m.exec(text)?.[1];
18367
+ const slug = /^brand_slug:\s*(\S+)\s*$/m.exec(text)?.[1];
18368
+ if (id)
18369
+ return { root: cur, id, name: slug ?? id, slug: slug ?? id };
18370
+ }
18371
+ const parent = dirname(cur);
18372
+ if (parent === cur)
18373
+ break;
18374
+ cur = parent;
18375
+ }
18376
+ return null;
18377
+ }
18378
+ function requireActive(opts) {
18320
18379
  const config = readConfig();
18321
18380
  if (!config) {
18322
18381
  throw new Error("Not logged in. Run `zalify login` first.");
18323
18382
  }
18383
+ const here = opts?.global ? null : checkoutWorkspace();
18324
18384
  if (config.version === 3) {
18385
+ if (here) {
18386
+ const known = config.workspacesCache?.find((w) => w.id === here.id) ?? null;
18387
+ return {
18388
+ appUrl: config.appUrl,
18389
+ key: config.userKey,
18390
+ workspaceId: here.id,
18391
+ workspaceName: known?.name ?? here.name,
18392
+ workspaceSlug: known?.slug ?? here.slug,
18393
+ config
18394
+ };
18395
+ }
18325
18396
  if (!config.active) {
18326
18397
  throw new Error("No active workspace. Run `zalify workspace list` then `zalify workspace set <slug>`.");
18327
18398
  }
@@ -18338,7 +18409,10 @@ function requireActive() {
18338
18409
  if (ids.length === 0) {
18339
18410
  throw new Error("Not logged in. Run `zalify login` first.");
18340
18411
  }
18341
- const workspaceId = config.activeWorkspaceId && config.workspaces[config.activeWorkspaceId] ? config.activeWorkspaceId : ids[0];
18412
+ if (here && !config.workspaces[here.id]) {
18413
+ throw new Error(`This folder belongs to workspace "${here.slug}" but your login has no key for it. ` + "Run `zalify login` again to refresh your credentials.");
18414
+ }
18415
+ const workspaceId = here?.id ?? (config.activeWorkspaceId && config.workspaces[config.activeWorkspaceId] ? config.activeWorkspaceId : ids[0]);
18342
18416
  const entry = config.workspaces[workspaceId];
18343
18417
  return {
18344
18418
  appUrl: config.appUrl,
@@ -18583,10 +18657,10 @@ async function whoami() {
18583
18657
  // src/assets.ts
18584
18658
  init_hash();
18585
18659
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync2, readdirSync } from "node:fs";
18586
- import { join as join2, resolve } from "node:path";
18660
+ import { join as join2, resolve as resolve2 } from "node:path";
18587
18661
  var BATCH = 10;
18588
18662
  function imagesDirFor(storeDir) {
18589
- const dir2 = join2(resolve(storeDir), "images");
18663
+ const dir2 = join2(resolve2(storeDir), "images");
18590
18664
  if (!existsSync2(dir2))
18591
18665
  throw new Error(`No images/ directory in ${storeDir}`);
18592
18666
  return dir2;
@@ -18920,7 +18994,7 @@ async function workspaceSet(slugOrId) {
18920
18994
  // src/images.ts
18921
18995
  init_hash();
18922
18996
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync4, rmSync as rmSync2 } from "node:fs";
18923
- import { join as join4, resolve as resolve2 } from "node:path";
18997
+ import { join as join4, resolve as resolve3 } from "node:path";
18924
18998
  var MAX_JOBS_PER_REQUEST = 40;
18925
18999
  var POLL_INTERVAL_MS = 5000;
18926
19000
  var POLL_TIMEOUT_MS = 30 * 60 * 1000;
@@ -19085,7 +19159,7 @@ async function streamingGenerate(auth, manifest, missing, imagesDir, indexPath,
19085
19159
  }
19086
19160
  async function imagesGenerate(storeDir) {
19087
19161
  const auth = requireActive();
19088
- const imagesDir = join4(resolve2(storeDir), "images");
19162
+ const imagesDir = join4(resolve3(storeDir), "images");
19089
19163
  const manifestPath = join4(imagesDir, "manifest.json");
19090
19164
  if (!existsSync4(manifestPath)) {
19091
19165
  throw new Error(`No images/manifest.json in ${storeDir}`);
@@ -19128,12 +19202,12 @@ async function imagesGenerate(storeDir) {
19128
19202
 
19129
19203
  // src/shopify.ts
19130
19204
  import { readFileSync as readFileSync6, existsSync as existsSync7 } from "node:fs";
19131
- import { basename as basename3, join as join7, resolve as resolve5 } from "node:path";
19205
+ import { basename as basename3, join as join7, resolve as resolve6 } from "node:path";
19132
19206
 
19133
19207
  // src/shop.ts
19134
19208
  import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
19135
19209
  import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
19136
- import { basename as basename2, join as join6, resolve as resolve4 } from "node:path";
19210
+ import { basename as basename2, join as join6, resolve as resolve5 } from "node:path";
19137
19211
 
19138
19212
  // src/theme.ts
19139
19213
  init_hash();
@@ -19153,7 +19227,7 @@ import {
19153
19227
  writeFileSync as writeFileSync5
19154
19228
  } from "node:fs";
19155
19229
  import { tmpdir } from "node:os";
19156
- import { basename, dirname, join as join5, resolve as resolve3 } from "node:path";
19230
+ import { basename, dirname as dirname2, join as join5, resolve as resolve4 } from "node:path";
19157
19231
  var TEMPLATES_PKG = "@zalify/theme-templates";
19158
19232
  var REGISTRY = "https://registry.npmjs.org";
19159
19233
  var MANIFEST_PATH = ".zalify/theme.json";
@@ -19185,7 +19259,7 @@ async function downloadTarball(url) {
19185
19259
  }
19186
19260
  function extractTarball(file2) {
19187
19261
  const dir2 = mkdtempSync(join5(tmpdir(), "zalify-theme-x-"));
19188
- const result = spawnSync2("tar", ["-xzf", resolve3(file2), "-C", dir2], {
19262
+ const result = spawnSync2("tar", ["-xzf", resolve4(file2), "-C", dir2], {
19189
19263
  encoding: "utf8"
19190
19264
  });
19191
19265
  if (result.error || result.status !== 0) {
@@ -19253,7 +19327,7 @@ function writeJson(path9, value) {
19253
19327
  `);
19254
19328
  }
19255
19329
  async function themeCreate(dir2, opts) {
19256
- const targetDir = resolve3(dir2);
19330
+ const targetDir = resolve4(dir2);
19257
19331
  if (existsSync5(targetDir) && readdirSync2(targetDir).length > 0) {
19258
19332
  throw new Error(`${dir2} already exists and is not empty`);
19259
19333
  }
@@ -19337,7 +19411,7 @@ Runs on mock.shop demo data out of the box — edit .env to connect your store.`
19337
19411
  console.log("Your theme customizations live in theme/ — see theme/README.md.");
19338
19412
  }
19339
19413
  function themeStatus(dir2 = ".") {
19340
- const projectDir = resolve3(dir2);
19414
+ const projectDir = resolve4(dir2);
19341
19415
  const manifest = readProjectManifest(projectDir);
19342
19416
  console.log(`${manifest.template} theme, ${TEMPLATES_PKG}@${manifest.version}` + (manifest.variant === "editor" ? " (editor variant)" : ""));
19343
19417
  let clean = 0;
@@ -19365,7 +19439,7 @@ function looksBinary(...buffers) {
19365
19439
  return buffers.some((b) => b.subarray(0, 8000).includes(0));
19366
19440
  }
19367
19441
  async function themeUpgrade(dir2 = ".", opts = {}) {
19368
- const projectDir = resolve3(dir2);
19442
+ const projectDir = resolve4(dir2);
19369
19443
  const local = readProjectManifest(projectDir);
19370
19444
  const variant = local.variant ?? "default";
19371
19445
  const target = await acquireTemplatePkg(opts.tarball, opts.to);
@@ -19405,7 +19479,7 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
19405
19479
  const write = (bytes) => {
19406
19480
  if (opts.dryRun)
19407
19481
  return;
19408
- mkdirSync2(dirname(localPath), { recursive: true });
19482
+ mkdirSync2(dirname2(localPath), { recursive: true });
19409
19483
  writeFileSync5(localPath, bytes);
19410
19484
  };
19411
19485
  if (oldHash && newHash) {
@@ -19629,7 +19703,7 @@ async function pollUntilReady(auth, id) {
19629
19703
  }
19630
19704
  async function shopCreate(dir2, opts) {
19631
19705
  const auth = requireActive();
19632
- const slug = basename2(resolve4(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "site";
19706
+ const slug = basename2(resolve5(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "site";
19633
19707
  console.log(`Provisioning shop "${slug}" (workspace "${auth.workspaceName}")`);
19634
19708
  const created = await request(auth, "POST", "/api/sites", {
19635
19709
  slug,
@@ -19650,17 +19724,17 @@ async function shopCreate(dir2, opts) {
19650
19724
  install: opts.install,
19651
19725
  git: opts.git
19652
19726
  });
19653
- writeFileSync6(join6(resolve4(dir2), ".zalify", "site.json"), JSON.stringify({ id: siteId, slug: view.slug, domain: view.domain, repo: view.githubRepo }, null, 2) + `
19727
+ writeFileSync6(join6(resolve5(dir2), ".zalify", "site.json"), JSON.stringify({ id: siteId, slug: view.slug, domain: view.domain, repo: view.githubRepo }, null, 2) + `
19654
19728
  `);
19655
- writeFileSync6(join6(resolve4(dir2), "theme", "pixel.json"), JSON.stringify({ workspaceId: auth.workspaceId }, null, 2) + `
19729
+ writeFileSync6(join6(resolve5(dir2), "theme", "pixel.json"), JSON.stringify({ workspaceId: auth.workspaceId }, null, 2) + `
19656
19730
  `);
19657
19731
  if (opts.git !== false && view.githubRepo) {
19658
19732
  const { token, repo } = await request(auth, "POST", `/api/sites/${siteId}/push-token`, {});
19659
- const run = (args) => spawnSync3("git", args, { cwd: resolve4(dir2), stdio: "ignore" });
19733
+ const run = (args) => spawnSync3("git", args, { cwd: resolve5(dir2), stdio: "ignore" });
19660
19734
  run(["add", ".zalify/site.json", "theme/pixel.json"]);
19661
19735
  run(["commit", "-m", "Link site record", "--no-verify"]);
19662
19736
  run(["remote", "add", "origin", `https://github.com/${repo}.git`]);
19663
- const push = spawnSync3("git", ["push", `https://x-access-token:${token}@github.com/${repo}.git`, "main"], { cwd: resolve4(dir2), stdio: "inherit" });
19737
+ const push = spawnSync3("git", ["push", `https://x-access-token:${token}@github.com/${repo}.git`, "main"], { cwd: resolve5(dir2), stdio: "inherit" });
19664
19738
  if (push.status !== 0) {
19665
19739
  throw new Error(`git push to ${repo} failed — resolve and push manually.`);
19666
19740
  }
@@ -19708,8 +19782,8 @@ async function resolveSite(auth, slugOrDir) {
19708
19782
  return null;
19709
19783
  };
19710
19784
  let slug = null;
19711
- if (slugOrDir && existsSync6(resolve4(slugOrDir)))
19712
- slug = fromDir(resolve4(slugOrDir));
19785
+ if (slugOrDir && existsSync6(resolve5(slugOrDir)))
19786
+ slug = fromDir(resolve5(slugOrDir));
19713
19787
  if (!slug && slugOrDir)
19714
19788
  slug = slugOrDir;
19715
19789
  if (!slug)
@@ -19769,7 +19843,7 @@ async function maybeRefreshShops(auth, brandDir, siteSlugOverride) {
19769
19843
  try {
19770
19844
  let slug = siteSlugOverride ?? null;
19771
19845
  if (!slug) {
19772
- const storeJson = join6(resolve4(brandDir), "store.json");
19846
+ const storeJson = join6(resolve5(brandDir), "store.json");
19773
19847
  if (existsSync6(storeJson)) {
19774
19848
  slug = JSON.parse(readFileSync5(storeJson, "utf8")).slug ?? null;
19775
19849
  }
@@ -19854,7 +19928,7 @@ async function findProductId(auth, handle) {
19854
19928
  }
19855
19929
  async function shopifyImport(storeDir, options = {}) {
19856
19930
  const auth = requireActive();
19857
- const dir2 = resolve5(storeDir);
19931
+ const dir2 = resolve6(storeDir);
19858
19932
  const catalog = readJson(join7(dir2, "catalog.json"), "catalog.json");
19859
19933
  const { locations } = await gql(auth, `{ locations(first: 1) { nodes { id name } } }`);
19860
19934
  const locationId = locations.nodes[0]?.id;
@@ -20063,7 +20137,7 @@ async function shopifyImport(storeDir, options = {}) {
20063
20137
  }
20064
20138
  async function shopifyUploadImages(storeDir, options = {}) {
20065
20139
  const auth = requireActive();
20066
- const dir2 = resolve5(storeDir);
20140
+ const dir2 = resolve6(storeDir);
20067
20141
  const manifest = readJson(join7(dir2, "images", "manifest.json"), "images/manifest.json");
20068
20142
  console.log(`Uploading images to the Shopify store connected to workspace "${auth.workspaceName}"`);
20069
20143
  for (const entry of manifest.images) {
@@ -20145,7 +20219,7 @@ async function shopifyUploadImages(storeDir, options = {}) {
20145
20219
  // src/ads.ts
20146
20220
  import { spawnSync as spawnSync4 } from "node:child_process";
20147
20221
  import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, writeFileSync as writeFileSync7 } from "node:fs";
20148
- import { basename as basename4, join as join8, relative, resolve as resolve6 } from "node:path";
20222
+ import { basename as basename4, join as join8, relative, resolve as resolve7 } from "node:path";
20149
20223
  import { createInterface } from "node:readline/promises";
20150
20224
  function apiError3(status, json) {
20151
20225
  if (json.code === "SLUG_TAKEN") {
@@ -20197,7 +20271,7 @@ async function pollUntilReady2(auth, id) {
20197
20271
  throw new Error("Provisioning timed out after 5 minutes — check the Temporal UI.");
20198
20272
  }
20199
20273
  function scaffoldStarter(dir2, slug, workspaceId) {
20200
- const root = resolve6(dir2);
20274
+ const root = resolve7(dir2);
20201
20275
  const write = (rel, content) => {
20202
20276
  const abs = join8(root, rel);
20203
20277
  mkdirSync3(join8(abs, ".."), { recursive: true });
@@ -20250,24 +20324,33 @@ mirrors decisions into \`decisions/\` and daily signal digests into
20250
20324
  `);
20251
20325
  }
20252
20326
  async function adsCreate(dir2) {
20253
- const auth = requireActive();
20254
- const slug = basename4(resolve6(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "brand";
20327
+ const auth = requireActive({ global: true });
20328
+ const slug = basename4(resolve7(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "brand";
20255
20329
  console.log(`Provisioning brand instance "${slug}" (workspace "${auth.workspaceName}")`);
20256
20330
  const created = await request2(auth, "POST", "/api/repos", { slug });
20257
20331
  const view = await pollUntilReady2(auth, created.repoId);
20258
20332
  console.log(` ✓ provisioned: ${view.githubRepo}`);
20259
20333
  scaffoldStarter(dir2, slug, auth.workspaceId);
20260
- mkdirSync3(join8(resolve6(dir2), ".zalify"), { recursive: true });
20261
- writeFileSync7(join8(resolve6(dir2), ".zalify", "brand-repo.json"), JSON.stringify({ id: created.repoId, slug: view.slug, repo: view.githubRepo }, null, 2) + `
20334
+ mkdirSync3(join8(resolve7(dir2), ".zalify"), { recursive: true });
20335
+ writeFileSync7(join8(resolve7(dir2), ".zalify", "brand-repo.json"), JSON.stringify({
20336
+ id: created.repoId,
20337
+ slug: view.slug,
20338
+ repo: view.githubRepo,
20339
+ workspace: {
20340
+ id: auth.workspaceId,
20341
+ name: auth.workspaceName,
20342
+ slug: auth.workspaceSlug
20343
+ }
20344
+ }, null, 2) + `
20262
20345
  `);
20263
20346
  if (view.githubRepo) {
20264
20347
  const { token, repo } = await request2(auth, "POST", `/api/repos/${created.repoId}/push-token`, {});
20265
- const run = (args) => spawnSync4("git", args, { cwd: resolve6(dir2), stdio: "ignore" });
20348
+ const run = (args) => spawnSync4("git", args, { cwd: resolve7(dir2), stdio: "ignore" });
20266
20349
  run(["init", "-b", "main"]);
20267
20350
  run(["add", "-A"]);
20268
20351
  run(["commit", "-m", "Scaffold brand instance", "--no-verify"]);
20269
20352
  run(["remote", "add", "origin", `https://github.com/${repo}.git`]);
20270
- const push = spawnSync4("git", ["push", `https://x-access-token:${token}@github.com/${repo}.git`, "main"], { cwd: resolve6(dir2), stdio: "inherit" });
20353
+ const push = spawnSync4("git", ["push", `https://x-access-token:${token}@github.com/${repo}.git`, "main"], { cwd: resolve7(dir2), stdio: "inherit" });
20271
20354
  if (push.status !== 0) {
20272
20355
  throw new Error(`git push to ${repo} failed — resolve and push manually.`);
20273
20356
  }
@@ -20284,7 +20367,7 @@ Next steps:
20284
20367
  }
20285
20368
  var CAMPAIGN_NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
20286
20369
  function campaignParent(account) {
20287
- const cwd = resolve6(".");
20370
+ const cwd = resolve7(".");
20288
20371
  const inCheckout = existsSync8(join8(cwd, ".zalify", "brand-repo.json")) || existsSync8(join8(cwd, "accounts"));
20289
20372
  if (!inCheckout) {
20290
20373
  throw new Error("Not inside a brand-instance checkout — run from the repo `zalify ads create` made.");
@@ -20342,7 +20425,7 @@ ad_sets:
20342
20425
  age: 18-65
20343
20426
  advantage: true
20344
20427
  `);
20345
- console.log(`✓ ${relative(resolve6("."), dir2)}/ is ready`);
20428
+ console.log(`✓ ${relative(resolve7("."), dir2)}/ is ready`);
20346
20429
  console.log(" 1. drag images into creatives/");
20347
20430
  console.log(" 2. fill primary_text and link in campaign.yaml");
20348
20431
  console.log(" 3. `zalify ads sync` when it lands — validate, diff, apply");
@@ -20381,7 +20464,7 @@ import {
20381
20464
  readFileSync as readFileSync8,
20382
20465
  writeFileSync as writeFileSync9
20383
20466
  } from "node:fs";
20384
- import { join as join10, resolve as resolve7 } from "node:path";
20467
+ import { join as join10, resolve as resolve8 } from "node:path";
20385
20468
  import { createInterface as createInterface2 } from "node:readline/promises";
20386
20469
 
20387
20470
  // node_modules/yaml/dist/index.js
@@ -20543,7 +20626,7 @@ async function adsCreativeGenerate(opts) {
20543
20626
 
20544
20627
  // src/ads-diff.ts
20545
20628
  function root() {
20546
- const cwd = resolve7(".");
20629
+ const cwd = resolve8(".");
20547
20630
  if (existsSync10(join10(cwd, ".zalify", "brand-repo.json")) || existsSync10(join10(cwd, "campaigns"))) {
20548
20631
  return cwd;
20549
20632
  }
@@ -20630,6 +20713,7 @@ ${s.creates} to create, ${s.updates} to update, ${s.unchanged} unchanged` + `${r
20630
20713
  async function adsDiff() {
20631
20714
  const auth = requireActive();
20632
20715
  const base = root();
20716
+ enforceWorkspaceLink(base, auth);
20633
20717
  const { files } = await collect(base);
20634
20718
  if (files.length === 0) {
20635
20719
  console.log("No campaign files — `zalify ads pull` or `zalify ads new` first.");
@@ -20647,6 +20731,7 @@ async function adsDiff() {
20647
20731
  async function adsApply(opts) {
20648
20732
  const auth = requireActive();
20649
20733
  const base = root();
20734
+ enforceWorkspaceLink(base, auth);
20650
20735
  const { files } = await collect(base);
20651
20736
  if (files.length === 0) {
20652
20737
  console.log("No campaign files — `zalify ads pull` or `zalify ads new` first.");
@@ -20710,10 +20795,10 @@ import {
20710
20795
  readFileSync as readFileSync9,
20711
20796
  writeFileSync as writeFileSync10
20712
20797
  } from "node:fs";
20713
- import { dirname as dirname2, join as join11, resolve as resolve8 } from "node:path";
20798
+ import { dirname as dirname3, join as join11, resolve as resolve9 } from "node:path";
20714
20799
  var sha = (s) => createHash2("sha256").update(s).digest("hex");
20715
20800
  function checkoutRoot() {
20716
- const cwd = resolve8(".");
20801
+ const cwd = resolve9(".");
20717
20802
  if (existsSync11(join11(cwd, ".zalify", "brand-repo.json")) || existsSync11(join11(cwd, "accounts")) || existsSync11(join11(cwd, "campaigns"))) {
20718
20803
  return cwd;
20719
20804
  }
@@ -20734,6 +20819,7 @@ function readBindings(root2) {
20734
20819
  async function adsPull(opts) {
20735
20820
  const auth = requireActive();
20736
20821
  const root2 = checkoutRoot();
20822
+ enforceWorkspaceLink(root2, auth);
20737
20823
  const params = new URLSearchParams;
20738
20824
  if (opts.account)
20739
20825
  params.set("account", opts.account);
@@ -20784,7 +20870,7 @@ async function adsPull(opts) {
20784
20870
  continue;
20785
20871
  }
20786
20872
  }
20787
- mkdirSync5(dirname2(abs), { recursive: true });
20873
+ mkdirSync5(dirname3(abs), { recursive: true });
20788
20874
  writeFileSync10(abs, f.yaml);
20789
20875
  bindings.pulled[f.path] = nextHash;
20790
20876
  written += 1;
@@ -20798,7 +20884,7 @@ async function adsPull(opts) {
20798
20884
  bindings.nodes[key] = v;
20799
20885
  }
20800
20886
  const bindingsPath = join11(root2, "accounts", ".bindings.json");
20801
- mkdirSync5(dirname2(bindingsPath), { recursive: true });
20887
+ mkdirSync5(dirname3(bindingsPath), { recursive: true });
20802
20888
  writeFileSync10(bindingsPath, `${JSON.stringify(bindings, null, 2)}
20803
20889
  `);
20804
20890
  const exported = new Set(result.files.map((f) => f.path));
@@ -20824,7 +20910,7 @@ Review with git diff; commit when it reads right.`);
20824
20910
  // src/ads-read.ts
20825
20911
  import { spawn as spawn4 } from "node:child_process";
20826
20912
  import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
20827
- import { join as join12, resolve as resolve9 } from "node:path";
20913
+ import { join as join12, resolve as resolve10 } from "node:path";
20828
20914
  async function serviceGet(auth, path9) {
20829
20915
  const res = await fetch(`${auth.appUrl}/api/autopilot/${path9}`, {
20830
20916
  headers: {
@@ -20912,7 +20998,7 @@ account changes: not tracked for this platform yet` : `
20912
20998
  account changes: ${a.byPerson} by people, ${a.byMeta} automated`);
20913
20999
  }
20914
21000
  function adsDigest(dir2) {
20915
- const root2 = resolve9(dir2 ?? ".");
21001
+ const root2 = resolve10(dir2 ?? ".");
20916
21002
  const path9 = join12(root2, "signals", "latest.json");
20917
21003
  if (!existsSync12(path9)) {
20918
21004
  throw new Error("signals/latest.json not found — run from a brand-instance checkout " + "(and pull: the service commits a digest daily).");
@@ -20956,7 +21042,7 @@ function adsOpen() {
20956
21042
  }
20957
21043
  async function adsDoctor(dir2) {
20958
21044
  const auth = requireActive();
20959
- const root2 = resolve9(dir2 ?? ".");
21045
+ const root2 = resolve10(dir2 ?? ".");
20960
21046
  let failed = 0;
20961
21047
  const ok = (msg) => console.log(` ✓ ${msg}`);
20962
21048
  const bad = (msg) => {
@@ -21042,7 +21128,7 @@ ${failed} problem${failed > 1 ? "s" : ""}.`);
21042
21128
 
21043
21129
  // src/brand.ts
21044
21130
  import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "node:fs";
21045
- import { basename as basename5, join as join13, resolve as resolve10 } from "node:path";
21131
+ import { basename as basename5, join as join13, resolve as resolve11 } from "node:path";
21046
21132
  var AUTHORING_MD = `# Brand authoring guide
21047
21133
 
21048
21134
  The rules for filling in this folder. An author — human or AI agent —
@@ -21343,7 +21429,7 @@ var MANIFEST_JSON = JSON.stringify({
21343
21429
  }, null, 2) + `
21344
21430
  `;
21345
21431
  function brandInit(dir2 = ".", opts = {}) {
21346
- const target = resolve10(dir2);
21432
+ const target = resolve11(dir2);
21347
21433
  const slug = basename5(target).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
21348
21434
  if (existsSync13(target) && readdirSync6(target).length > 0) {
21349
21435
  throw new Error(`${dir2} already exists and is not empty`);
@@ -21366,7 +21452,7 @@ Author in this order: brand.md → catalog.json → images/manifest.json` + `
21366
21452
  }
21367
21453
  var MANIFEST_SIZES = new Set(["1024x1024", "1536x1024", "1024x1536"]);
21368
21454
  function brandValidate(dir2 = ".") {
21369
- const target = resolve10(dir2);
21455
+ const target = resolve11(dir2);
21370
21456
  const problems = [];
21371
21457
  const warnings = [];
21372
21458
  const readJson2 = (rel) => {
@@ -21556,7 +21642,7 @@ function brandValidate(dir2 = ".") {
21556
21642
  }
21557
21643
 
21558
21644
  // src/cli.ts
21559
- var __dirname4 = dirname3(fileURLToPath3(import.meta.url));
21645
+ var __dirname4 = dirname4(fileURLToPath3(import.meta.url));
21560
21646
  var require2 = createRequire2(import.meta.url);
21561
21647
  var pkg = require2(join14(__dirname4, "..", "package.json"));
21562
21648
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.24.1",
3
+ "version": "0.26.0",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",