@zalify/cli 0.23.0 → 0.24.1

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 +2 -2
  2. package/dist/cli.js +138 -65
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -101,8 +101,8 @@ its new home under `shop`.
101
101
  | --- | --- |
102
102
  | `ads create <dir>` | Provision an Autopilot brand-instance GitHub repo (POST `/api/repos`, GitHub App), scaffold the starter locally (`identity.yaml`, `brand.yaml`, `policy/`, `campaigns/`, `media-plans/`, `signals/`, `decisions/`), and push with a short-lived repo-scoped token. Writes `.zalify/brand-repo.json` so later commands run arg-free. The directory name becomes the slug — use the workspace's store slug so the service's digest writer can find the repo |
103
103
  | `ads` / `ads list` | List the workspace's brand instances and their provisioning status |
104
- | `ads new <name> [--objective] [--budget] [--country] [--link]` | Scaffold a campaign FOLDER the drag-and-drop form: `campaign.yaml` for budget/audience/copy defaults, `creatives/` for the files that become ads. Three questions when interactive, flags otherwise. `cp -r` an existing folder to duplicate a campaign |
105
- | `ads pull [--account <act_id>] [--days <n>] [--force]` | The existing account as files: every ingested campaign lands as `campaigns/<slug>.yaml` (imported form — names in `label:`, budgets, statuses, core audience) plus `campaigns/.bindings.json` mapping YAML nodes to platform ids. A hash ledger protects local edits: unedited files fast-forward, edited ones are kept and named, deletions never happen automatically |
104
+ | `ads new <name> [--account <folder>] [--objective] [--budget] [--country] [--link]` | Scaffold a campaign FOLDER at `accounts/<account>/<name>/` (account inferred when only one exists): `campaign.yaml` for budget/audience/copy defaults, `creatives/` for the files that become ads. `cp -r` an existing folder to duplicate a campaign |
105
+ | `ads pull [--account <act_id>] [--days <n>] [--force]` | The existing accounts as a tree: every ingested campaign lands as `accounts/<account-name>/<campaign>/campaign.yaml` (imported form — names in `label:`, budgets, statuses, core audience) plus `campaigns/.bindings.json` mapping YAML nodes to platform ids. A hash ledger protects local edits: unedited files fast-forward, edited ones are kept and named, deletions never happen automatically |
106
106
  | `ads creative generate <campaign> --prompt <text> [--orientation] [--ref <url...>] [--name]` | One image through the canvas pipeline (safe zones, product fidelity with refs), downloaded INTO the campaign's `creatives/` and recorded in its `assets.json` — the filesystem stays the interface, and the asset is stored once |
107
107
  | `ads diff` | Desired (your campaign files) vs actual (the ingested account), keyed through bindings: bound nodes compare budget/status, unbound nodes become creations. Folder campaigns upload their `creatives/` first (presign + checksum dedup — the `brand images push` machinery) and derive server-side. Read-only against the platform |
108
108
  | `ads apply [-y] [--activate]` | Execute the diff after showing it and asking. Creations land **PAUSED** — always; budget edits apply to bound entities; `paused→active` is refused without `--activate` (turning spend on is said twice; `active→paused` always executes). Created ids merge into the bindings ledger |
package/dist/cli.js CHANGED
@@ -20144,8 +20144,8 @@ async function shopifyUploadImages(storeDir, options = {}) {
20144
20144
 
20145
20145
  // src/ads.ts
20146
20146
  import { spawnSync as spawnSync4 } from "node:child_process";
20147
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync7 } from "node:fs";
20148
- import { basename as basename4, join as join8, resolve as resolve6 } from "node:path";
20147
+ 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";
20149
20149
  import { createInterface } from "node:readline/promises";
20150
20150
  function apiError3(status, json) {
20151
20151
  if (json.code === "SLUG_TAKEN") {
@@ -20283,14 +20283,22 @@ Next steps:
20283
20283
  ` + ` - the Autopilot service picks the instance up from there`);
20284
20284
  }
20285
20285
  var CAMPAIGN_NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
20286
- function campaignsDir() {
20286
+ function campaignParent(account) {
20287
20287
  const cwd = resolve6(".");
20288
- if (existsSync8(join8(cwd, "campaigns")))
20289
- return join8(cwd, "campaigns");
20290
- if (existsSync8(join8(cwd, ".zalify", "brand-repo.json"))) {
20291
- return join8(cwd, "campaigns");
20292
- }
20293
- throw new Error("Not inside a brand-instance checkout — run from the repo `zalify ads create` made.");
20288
+ const inCheckout = existsSync8(join8(cwd, ".zalify", "brand-repo.json")) || existsSync8(join8(cwd, "accounts"));
20289
+ if (!inCheckout) {
20290
+ throw new Error("Not inside a brand-instance checkout — run from the repo `zalify ads create` made.");
20291
+ }
20292
+ const accountsDir = join8(cwd, "accounts");
20293
+ if (account)
20294
+ return join8(accountsDir, account);
20295
+ const dirs = existsSync8(accountsDir) ? readdirSync3(accountsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name) : [];
20296
+ if (dirs.length === 1)
20297
+ return join8(accountsDir, dirs[0]);
20298
+ if (dirs.length === 0) {
20299
+ throw new Error("No account folders yet — `zalify ads pull` once, or pass --account <folder>.");
20300
+ }
20301
+ throw new Error(`Several accounts (${dirs.join(", ")}) — pass --account <folder>.`);
20294
20302
  }
20295
20303
  async function ask(question, fallback) {
20296
20304
  if (!process.stdin.isTTY)
@@ -20304,9 +20312,10 @@ async function adsNew(name, opts) {
20304
20312
  if (!CAMPAIGN_NAME_RE.test(name) || name.length > 63) {
20305
20313
  throw new Error(`"${name}" — campaign names are lowercase letters, digits, and inner hyphens.`);
20306
20314
  }
20307
- const dir2 = join8(campaignsDir(), name);
20315
+ const parent = campaignParent(opts.account);
20316
+ const dir2 = join8(parent, name);
20308
20317
  if (existsSync8(dir2))
20309
- throw new Error(`campaigns/${name}/ already exists.`);
20318
+ throw new Error(`${dir2} already exists.`);
20310
20319
  const objective = opts.objective ?? await ask("objective (sales/leads/traffic/awareness)", "sales");
20311
20320
  const budget = opts.budget ?? await ask("budget", "$20/day");
20312
20321
  const country = opts.country ?? await ask("country", "US");
@@ -20333,7 +20342,7 @@ ad_sets:
20333
20342
  age: 18-65
20334
20343
  advantage: true
20335
20344
  `);
20336
- console.log(`✓ campaigns/${name}/ is ready`);
20345
+ console.log(`✓ ${relative(resolve6("."), dir2)}/ is ready`);
20337
20346
  console.log(" 1. drag images into creatives/");
20338
20347
  console.log(" 2. fill primary_text and link in campaign.yaml");
20339
20348
  console.log(" 3. `zalify ads sync` when it lands — validate, diff, apply");
@@ -20365,7 +20374,13 @@ async function adsList() {
20365
20374
  }
20366
20375
 
20367
20376
  // src/ads-diff.ts
20368
- import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "node:fs";
20377
+ import {
20378
+ existsSync as existsSync10,
20379
+ mkdirSync as mkdirSync4,
20380
+ readdirSync as readdirSync5,
20381
+ readFileSync as readFileSync8,
20382
+ writeFileSync as writeFileSync9
20383
+ } from "node:fs";
20369
20384
  import { join as join10, resolve as resolve7 } from "node:path";
20370
20385
  import { createInterface as createInterface2 } from "node:readline/promises";
20371
20386
 
@@ -20416,11 +20431,36 @@ var $visit = visit.visit;
20416
20431
  var $visitAsync = visit.visitAsync;
20417
20432
 
20418
20433
  // src/ads-sync.ts
20419
- import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
20434
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
20420
20435
  import { join as join9 } from "node:path";
20421
20436
  var CREATIVE_EXT = /\.(png|jpe?g|webp)$/i;
20422
- async function collectFolder(base, name) {
20423
- const dir2 = join9(base, "campaigns", name);
20437
+ function campaignDirs(base) {
20438
+ const found = [];
20439
+ const accounts = join9(base, "accounts");
20440
+ if (existsSync9(accounts)) {
20441
+ for (const acct of readdirSync4(accounts, { withFileTypes: true })) {
20442
+ if (!acct.isDirectory())
20443
+ continue;
20444
+ const acctDir = join9(accounts, acct.name);
20445
+ for (const c of readdirSync4(acctDir, { withFileTypes: true })) {
20446
+ if (c.isDirectory() && existsSync9(join9(acctDir, c.name, "campaign.yaml"))) {
20447
+ found.push(`accounts/${acct.name}/${c.name}`);
20448
+ }
20449
+ }
20450
+ }
20451
+ }
20452
+ const legacy = join9(base, "campaigns");
20453
+ if (existsSync9(legacy)) {
20454
+ for (const e of readdirSync4(legacy, { withFileTypes: true })) {
20455
+ if (e.isDirectory() && existsSync9(join9(legacy, e.name, "campaign.yaml"))) {
20456
+ found.push(`campaigns/${e.name}`);
20457
+ }
20458
+ }
20459
+ }
20460
+ return found;
20461
+ }
20462
+ async function collectFolder(base, rel) {
20463
+ const dir2 = join9(base, rel);
20424
20464
  const doc = $parse(readFileSync7(join9(dir2, "campaign.yaml"), "utf8"));
20425
20465
  const creativesDir = join9(dir2, "creatives");
20426
20466
  const sources = [];
@@ -20428,7 +20468,7 @@ async function collectFolder(base, name) {
20428
20468
  const abs = join9(creativesDir, sub);
20429
20469
  if (!existsSync9(abs))
20430
20470
  return;
20431
- const entries = readdirSync3(abs, { withFileTypes: true });
20471
+ const entries = readdirSync4(abs, { withFileTypes: true });
20432
20472
  const hasImages = entries.some((e) => e.isFile() && CREATIVE_EXT.test(e.name));
20433
20473
  const index = hasImages ? await pushImagesIn(abs) : {};
20434
20474
  for (const e of entries) {
@@ -20448,19 +20488,20 @@ async function collectFolder(base, name) {
20448
20488
  }
20449
20489
  };
20450
20490
  await gather("", "");
20451
- return { path: `campaigns/${name}/campaign.yaml`, doc, sources };
20452
- }
20453
- function folderCampaigns(base) {
20454
- const dir2 = join9(base, "campaigns");
20455
- if (!existsSync9(dir2))
20456
- return [];
20457
- return readdirSync3(dir2, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync9(join9(dir2, e.name, "campaign.yaml"))).map((e) => e.name);
20491
+ return { path: `${rel}/campaign.yaml`, doc, sources };
20458
20492
  }
20459
20493
  async function adsCreativeGenerate(opts) {
20460
20494
  const auth = requireActive();
20461
- const dir2 = join9(".", "campaigns", opts.campaign, "creatives");
20495
+ const matches = campaignDirs(".").filter((rel) => rel.split("/").pop() === opts.campaign);
20496
+ if (matches.length === 0) {
20497
+ throw new Error(`No campaign folder "${opts.campaign}" — \`zalify ads new ${opts.campaign}\` first.`);
20498
+ }
20499
+ if (matches.length > 1) {
20500
+ throw new Error(`"${opts.campaign}" exists under several accounts: ${matches.join(", ")} — cd closer or rename.`);
20501
+ }
20502
+ const dir2 = join9(".", matches[0], "creatives");
20462
20503
  if (!existsSync9(dir2)) {
20463
- throw new Error(`campaigns/${opts.campaign}/creatives/ not found — \`zalify ads new ${opts.campaign}\` first.`);
20504
+ throw new Error(`${matches[0]}/creatives/ not found.`);
20464
20505
  }
20465
20506
  console.log("Generating (the canvas pipeline: safe zones + product fidelity)…");
20466
20507
  const res = await fetch(`${auth.appUrl}/api/autopilot/ads-code/creative`, {
@@ -20509,34 +20550,41 @@ function root() {
20509
20550
  throw new Error("Not inside a brand-instance checkout — run from the repo `zalify ads create` made.");
20510
20551
  }
20511
20552
  async function collect(base) {
20512
- const dir2 = join10(base, "campaigns");
20513
- if (!existsSync10(dir2))
20514
- return { files: [] };
20515
20553
  const files = [];
20516
- for (const entry of readdirSync4(dir2, { withFileTypes: true })) {
20517
- if (entry.isDirectory())
20518
- continue;
20519
- if (!entry.name.endsWith(".yaml") || entry.name.endsWith(".copy.yaml")) {
20520
- continue;
20521
- }
20522
- const path9 = `campaigns/${entry.name}`;
20523
- try {
20524
- files.push({ path: path9, doc: $parse(readFileSync8(join10(base, path9), "utf8")) });
20525
- } catch (err) {
20526
- throw new Error(`${path9} is not valid YAML: ${String(err).slice(0, 120)}`);
20554
+ const legacy = join10(base, "campaigns");
20555
+ if (existsSync10(legacy)) {
20556
+ for (const entry of readdirSync5(legacy, { withFileTypes: true })) {
20557
+ if (entry.isDirectory())
20558
+ continue;
20559
+ if (!entry.name.endsWith(".yaml") || entry.name.endsWith(".copy.yaml")) {
20560
+ continue;
20561
+ }
20562
+ const path9 = `campaigns/${entry.name}`;
20563
+ try {
20564
+ files.push({ path: path9, doc: $parse(readFileSync8(join10(base, path9), "utf8")) });
20565
+ } catch (err) {
20566
+ throw new Error(`${path9} is not valid YAML: ${String(err).slice(0, 120)}`);
20567
+ }
20527
20568
  }
20528
20569
  }
20529
- for (const name of folderCampaigns(base)) {
20530
- files.push(await collectFolder(base, name));
20570
+ for (const rel of campaignDirs(base)) {
20571
+ const payload = await collectFolder(base, rel);
20572
+ const imported = typeof payload.doc === "object" && payload.doc !== null && payload.doc.imported === true;
20573
+ files.push(imported ? { path: payload.path, doc: payload.doc } : payload);
20531
20574
  }
20532
20575
  return { files };
20533
20576
  }
20577
+ function ledgerPath(base) {
20578
+ return join10(base, "accounts", ".bindings.json");
20579
+ }
20534
20580
  function ledger(base) {
20535
- const path9 = join10(base, "campaigns", ".bindings.json");
20536
- if (!existsSync10(path9))
20537
- return { nodes: {}, pulled: {} };
20538
- const raw = JSON.parse(readFileSync8(path9, "utf8"));
20539
- return { nodes: raw.nodes ?? {}, pulled: raw.pulled ?? {} };
20581
+ for (const path9 of [ledgerPath(base), join10(base, "campaigns", ".bindings.json")]) {
20582
+ if (existsSync10(path9)) {
20583
+ const raw = JSON.parse(readFileSync8(path9, "utf8"));
20584
+ return { nodes: raw.nodes ?? {}, pulled: raw.pulled ?? {} };
20585
+ }
20586
+ }
20587
+ return { nodes: {}, pulled: {} };
20540
20588
  }
20541
20589
  async function post(auth, path9, payload) {
20542
20590
  const res = await fetch(`${auth.appUrl}/api/autopilot/${path9}`, {
@@ -20644,7 +20692,8 @@ ${result.refusedActivations} activation${result.refusedActivations > 1 ? "s" : "
20644
20692
  }
20645
20693
  if (Object.keys(result.newBindings).length > 0) {
20646
20694
  Object.assign(led.nodes, result.newBindings);
20647
- writeFileSync9(join10(base, "campaigns", ".bindings.json"), `${JSON.stringify(led, null, 2)}
20695
+ mkdirSync4(join10(base, "accounts"), { recursive: true });
20696
+ writeFileSync9(ledgerPath(base), `${JSON.stringify(led, null, 2)}
20648
20697
  `);
20649
20698
  console.log(`
20650
20699
  ${Object.keys(result.newBindings).length} new bindings recorded — everything created is PAUSED.`);
@@ -20657,7 +20706,7 @@ ${Object.keys(result.newBindings).length} new bindings recorded — everything c
20657
20706
  import { createHash as createHash2 } from "node:crypto";
20658
20707
  import {
20659
20708
  existsSync as existsSync11,
20660
- mkdirSync as mkdirSync4,
20709
+ mkdirSync as mkdirSync5,
20661
20710
  readFileSync as readFileSync9,
20662
20711
  writeFileSync as writeFileSync10
20663
20712
  } from "node:fs";
@@ -20665,17 +20714,22 @@ import { dirname as dirname2, join as join11, resolve as resolve8 } from "node:p
20665
20714
  var sha = (s) => createHash2("sha256").update(s).digest("hex");
20666
20715
  function checkoutRoot() {
20667
20716
  const cwd = resolve8(".");
20668
- if (existsSync11(join11(cwd, ".zalify", "brand-repo.json")) || existsSync11(join11(cwd, "campaigns"))) {
20717
+ if (existsSync11(join11(cwd, ".zalify", "brand-repo.json")) || existsSync11(join11(cwd, "accounts")) || existsSync11(join11(cwd, "campaigns"))) {
20669
20718
  return cwd;
20670
20719
  }
20671
20720
  throw new Error("Not inside a brand-instance checkout — run from the repo `zalify ads create` made.");
20672
20721
  }
20673
20722
  function readBindings(root2) {
20674
- const path9 = join11(root2, "campaigns", ".bindings.json");
20675
- if (!existsSync11(path9))
20676
- return { nodes: {}, pulled: {} };
20677
- const raw = JSON.parse(readFileSync9(path9, "utf8"));
20678
- return { nodes: raw.nodes ?? {}, pulled: raw.pulled ?? {} };
20723
+ for (const path9 of [
20724
+ join11(root2, "accounts", ".bindings.json"),
20725
+ join11(root2, "campaigns", ".bindings.json")
20726
+ ]) {
20727
+ if (existsSync11(path9)) {
20728
+ const raw = JSON.parse(readFileSync9(path9, "utf8"));
20729
+ return { nodes: raw.nodes ?? {}, pulled: raw.pulled ?? {} };
20730
+ }
20731
+ }
20732
+ return { nodes: {}, pulled: {} };
20679
20733
  }
20680
20734
  async function adsPull(opts) {
20681
20735
  const auth = requireActive();
@@ -20698,10 +20752,22 @@ async function adsPull(opts) {
20698
20752
  }
20699
20753
  const result = await res.json();
20700
20754
  const bindings = readBindings(root2);
20755
+ const ownedElsewhere = new Map;
20756
+ for (const [key, v] of Object.entries(bindings.nodes)) {
20757
+ if (v.kind === "campaign")
20758
+ ownedElsewhere.set(v.id, key.split("#")[0]);
20759
+ }
20701
20760
  let written = 0;
20702
20761
  let unchanged = 0;
20762
+ let owned = 0;
20703
20763
  const skipped = [];
20704
20764
  for (const f of result.files) {
20765
+ const campaignId = result.bindings[`${f.path}#campaign`]?.id;
20766
+ const owner = campaignId ? ownedElsewhere.get(campaignId) : undefined;
20767
+ if (owner && owner !== f.path) {
20768
+ owned += 1;
20769
+ continue;
20770
+ }
20705
20771
  const abs = join11(root2, f.path);
20706
20772
  const nextHash = sha(f.yaml);
20707
20773
  const lastPulled = bindings.pulled[f.path];
@@ -20712,26 +20778,33 @@ async function adsPull(opts) {
20712
20778
  bindings.pulled[f.path] = nextHash;
20713
20779
  continue;
20714
20780
  }
20715
- const locallyEdited = lastPulled !== undefined && sha(current) !== lastPulled;
20781
+ const locallyEdited = lastPulled === undefined || sha(current) !== lastPulled;
20716
20782
  if (locallyEdited && !opts.force) {
20717
20783
  skipped.push(f.path);
20718
20784
  continue;
20719
20785
  }
20720
20786
  }
20721
- mkdirSync4(dirname2(abs), { recursive: true });
20787
+ mkdirSync5(dirname2(abs), { recursive: true });
20722
20788
  writeFileSync10(abs, f.yaml);
20723
20789
  bindings.pulled[f.path] = nextHash;
20724
20790
  written += 1;
20725
20791
  }
20726
- Object.assign(bindings.nodes, result.bindings);
20727
- const bindingsPath = join11(root2, "campaigns", ".bindings.json");
20728
- mkdirSync4(dirname2(bindingsPath), { recursive: true });
20792
+ for (const [key, v] of Object.entries(result.bindings)) {
20793
+ const file2 = key.split("#")[0];
20794
+ const campaignId = result.bindings[`${file2}#campaign`]?.id;
20795
+ const owner = campaignId ? ownedElsewhere.get(campaignId) : undefined;
20796
+ if (owner && owner !== file2)
20797
+ continue;
20798
+ bindings.nodes[key] = v;
20799
+ }
20800
+ const bindingsPath = join11(root2, "accounts", ".bindings.json");
20801
+ mkdirSync5(dirname2(bindingsPath), { recursive: true });
20729
20802
  writeFileSync10(bindingsPath, `${JSON.stringify(bindings, null, 2)}
20730
20803
  `);
20731
20804
  const exported = new Set(result.files.map((f) => f.path));
20732
20805
  const gone = Object.keys(bindings.pulled).filter((p) => !exported.has(p) && existsSync11(join11(root2, p)));
20733
20806
  const s = result.summary;
20734
- console.log(`pulled ${s.campaigns} campaigns (${s.adSets} ad sets, ${s.ads} ads): ` + `${written} written, ${unchanged} unchanged, ${skipped.length} kept (locally edited)`);
20807
+ console.log(`pulled ${s.campaigns} campaigns (${s.adSets} ad sets, ${s.ads} ads): ` + `${written} written, ${unchanged} unchanged, ${skipped.length} kept (locally edited)` + `${owned ? `, ${owned} owned by authored files` : ""}`);
20735
20808
  for (const p of skipped)
20736
20809
  console.log(` kept ${p} (edited — --force overwrites)`);
20737
20810
  if (gone.length > 0) {
@@ -20968,7 +21041,7 @@ ${failed} problem${failed > 1 ? "s" : ""}.`);
20968
21041
  }
20969
21042
 
20970
21043
  // src/brand.ts
20971
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "node:fs";
21044
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "node:fs";
20972
21045
  import { basename as basename5, join as join13, resolve as resolve10 } from "node:path";
20973
21046
  var AUTHORING_MD = `# Brand authoring guide
20974
21047
 
@@ -21272,12 +21345,12 @@ var MANIFEST_JSON = JSON.stringify({
21272
21345
  function brandInit(dir2 = ".", opts = {}) {
21273
21346
  const target = resolve10(dir2);
21274
21347
  const slug = basename5(target).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
21275
- if (existsSync13(target) && readdirSync5(target).length > 0) {
21348
+ if (existsSync13(target) && readdirSync6(target).length > 0) {
21276
21349
  throw new Error(`${dir2} already exists and is not empty`);
21277
21350
  }
21278
21351
  const name = opts.name ?? slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
21279
21352
  const currency = (opts.currency ?? "USD").toUpperCase();
21280
- mkdirSync5(join13(target, "images"), { recursive: true });
21353
+ mkdirSync6(join13(target, "images"), { recursive: true });
21281
21354
  writeFileSync11(join13(target, "AUTHORING.md"), AUTHORING_MD);
21282
21355
  writeFileSync11(join13(target, "brand.md"), BRAND_MD(name));
21283
21356
  writeFileSync11(join13(target, "store.json"), STORE_JSON(name, slug, opts.vertical ?? "<vertical>", currency));
@@ -21567,7 +21640,7 @@ ads.command("list", { isDefault: true }).description("List the workspace's brand
21567
21640
  ads.command("remove <slug>").description("Deprecate a brand instance (record soft-deleted; the GitHub repo is left untouched)").action(async (slug) => {
21568
21641
  await adsRemove(slug);
21569
21642
  });
21570
- ads.command("new <name>").description("Scaffold a campaign folder — drop images into creatives/, then sync").option("--objective <o>", "sales | leads | traffic | awareness").option("--budget <b>", '"$60/day" or "$900 total"').option("--country <cc>", "comma-separated ISO codes, e.g. US,CA").option("--link <url>", "destination URL for creative_defaults").action(async (name, options) => {
21643
+ ads.command("new <name>").description("Scaffold a campaign folder — drop images into creatives/, then sync").option("--objective <o>", "sales | leads | traffic | awareness").option("--budget <b>", '"$60/day" or "$900 total"').option("--country <cc>", "comma-separated ISO codes, e.g. US,CA").option("--link <url>", "destination URL for creative_defaults").option("--account <folder>", "account folder under accounts/ (inferred when only one)").action(async (name, options) => {
21571
21644
  await adsNew(name, options);
21572
21645
  });
21573
21646
  ads.command("pull").description("The existing account as files — campaigns/<slug>.yaml + bindings, from what the service has ingested").option("--account <act_id>", "one ad account only").option("--days <n>", "ads seen in the trailing window (default 30)").option("--force", "overwrite locally edited files").action(async (options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",