@erdoai/cli 0.71.0 → 0.72.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/index.js +115 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -332,6 +332,33 @@ var ErdoClient = class {
332
332
  { successor_manager_slug: successorManagerSlug }
333
333
  );
334
334
  }
335
+ // Reads the Google Ads container a managed org already has under this manager
336
+ // and creates nothing, so it is safe to run against an org mid-setup: a 404
337
+ // means the org has no container here, not that anything is broken.
338
+ getManagedOrganizationAdsContainer(slug) {
339
+ return this.request(
340
+ "GET",
341
+ `/v1/managed-organizations/${encodeURIComponent(slug)}/ads-container`
342
+ );
343
+ }
344
+ // Creates the container — a sub-account under the manager's MCC plus the
345
+ // delegated connection inside the managed org. `replaceCurrentAccount` is the
346
+ // one input with consequences: provisioning refuses outright when the org is
347
+ // already running campaigns in another account, and passing this accepts that
348
+ // those campaigns are left behind, so the key is sent only when it is asked
349
+ // for rather than as a `false` the server has to read.
350
+ provisionManagedOrganizationAdsContainer(slug, input) {
351
+ const body = {};
352
+ if (input.descriptiveName) body.descriptive_name = input.descriptiveName;
353
+ if (input.currencyCode) body.currency_code = input.currencyCode;
354
+ if (input.timeZone) body.time_zone = input.timeZone;
355
+ if (input.replaceCurrentAccount) body.replace_current_account = true;
356
+ return this.request(
357
+ "POST",
358
+ `/v1/managed-organizations/${encodeURIComponent(slug)}/ads-container`,
359
+ body
360
+ );
361
+ }
335
362
  // --- Custom page domains: the branded hostnames the org's pages serve from ---
336
363
  listCustomDomains() {
337
364
  return this.request("GET", "/v1/custom-domains");
@@ -2001,6 +2028,76 @@ managed.command("add-member <orgSlug> <email>").description("Seat a person in a
2001
2028
  fail(e);
2002
2029
  }
2003
2030
  });
2031
+ function printAdsContainer(c) {
2032
+ console.log(`org: ${c.org_slug}`);
2033
+ console.log(`customer id: ${c.customer_id}`);
2034
+ console.log(`manager (login customer id): ${c.login_customer_id}`);
2035
+ console.log(`integration: ${c.integration_id}`);
2036
+ if (c.descriptive_name) {
2037
+ console.log(`name: ${c.descriptive_name}`);
2038
+ }
2039
+ console.log(`billing: ${c.billing_status}`);
2040
+ if (c.billing_status === "unknown") {
2041
+ process.stderr.write(
2042
+ `Billing could not be checked for ${c.customer_id} \u2014 open the account in the Google Ads UI and confirm; this is not evidence that billing is missing.
2043
+ `
2044
+ );
2045
+ } else if (!c.billing_configured) {
2046
+ process.stderr.write(
2047
+ `Billing is ${c.billing_status}: ${c.customer_id} cannot serve until a payments profile is linked to it in the Google Ads UI.
2048
+ `
2049
+ );
2050
+ }
2051
+ }
2052
+ managed.command("ads-container <orgSlug>").description(
2053
+ "Read the Google Ads account a managed org runs its campaigns in, or --provision one under your manager account"
2054
+ ).option("--provision", "create the account and the org's delegated google_ads connection instead of reading them").option(
2055
+ "--replace-current-account",
2056
+ "with --provision: go ahead even though the org already runs campaigns in another account. Google Ads cannot move a campaign, so those campaigns stay behind and have to be rebuilt"
2057
+ ).option("--name <descriptiveName>", "display name for the new account (default: the org's name and slug)").option("--currency <code>", "ISO currency code for the new account, e.g. USD").option("--time-zone <tz>", "IANA time zone for the new account, e.g. America/New_York").action(
2058
+ async (orgSlug, opts) => {
2059
+ try {
2060
+ if (opts.replaceCurrentAccount && !opts.provision) {
2061
+ fail(new Error("--replace-current-account only applies with --provision"));
2062
+ }
2063
+ const api = new ErdoClient();
2064
+ if (!opts.provision) {
2065
+ try {
2066
+ printAdsContainer(await api.getManagedOrganizationAdsContainer(orgSlug));
2067
+ } catch (e) {
2068
+ if (e instanceof ErdoApiError && e.status === 404) {
2069
+ fail(
2070
+ new Error(
2071
+ `${orgSlug} has no Google Ads container under this manager. Create one with: erdo org managed ads-container ${orgSlug} --provision`
2072
+ )
2073
+ );
2074
+ }
2075
+ throw e;
2076
+ }
2077
+ return;
2078
+ }
2079
+ const c = await api.provisionManagedOrganizationAdsContainer(orgSlug, {
2080
+ descriptiveName: opts.name,
2081
+ currencyCode: opts.currency,
2082
+ timeZone: opts.timeZone,
2083
+ replaceCurrentAccount: opts.replaceCurrentAccount
2084
+ });
2085
+ console.log(
2086
+ c.already_provisioned ? `Google Ads container already existed for ${c.org_slug}` : `Created Google Ads container for ${c.org_slug}`
2087
+ );
2088
+ printAdsContainer(c);
2089
+ const leftBehind = c.previous_campaign_count ?? 0;
2090
+ if (c.previous_customer_id && leftBehind > 0) {
2091
+ process.stderr.write(
2092
+ `${leftBehind} campaign${leftBehind === 1 ? "" : "s"} stayed in ${c.previous_customer_id}, the account this replaced. Google Ads cannot move a campaign between accounts and nothing in ${c.org_slug} resolves that account any more, so they have to be rebuilt in ${c.customer_id}.
2093
+ `
2094
+ );
2095
+ }
2096
+ } catch (e) {
2097
+ fail(e);
2098
+ }
2099
+ }
2100
+ );
2004
2101
  managed.command("handoff-token <orgSlug>").description("Mint a one-time token offering an ownerless managed org to another manager; shown ONCE").requiredOption("--successor <managerOrgSlug>", "the manager org allowed to redeem the token").action(async (orgSlug, opts) => {
2005
2102
  try {
2006
2103
  const res = await new ErdoClient().createManagedOrganizationHandoffToken(orgSlug, opts.successor);
@@ -4009,7 +4106,13 @@ function grantList(v) {
4009
4106
  pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/--css accept @file").requiredOption("--title <title>", "page title").requiredOption("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--runtime <runtime>", "react-tailwind (default) or none").option(
4010
4107
  "--meta-title <title>",
4011
4108
  "public SEO title (browser tab + shared-link headline) \u2014 distinct from --title, the internal name"
4012
- ).option("--meta-description <description>", "public SEO description (shared-link preview / search result blurb)").option("--datasets <csv>", "dataset slugs the page reads").option("--writable-datasets <csv>", "dataset slugs the page may write via erdo.insertRows").option("--kv <csv>", "named KV-store slugs the page reads").option("--writable-kv <csv>", "named KV-store slugs the page may write").option("--public", "make the page publicly viewable").action(
4109
+ ).option("--meta-description <description>", "public SEO description (shared-link preview / search result blurb)").option(
4110
+ "--meta-icon-asset <attachmentId>",
4111
+ "public favicon: a Brand Style attachment id (apple-icon-180x180 / favicon-32x32) for the browser-tab icon"
4112
+ ).option(
4113
+ "--meta-image-asset <attachmentId>",
4114
+ "public share image: a Brand Style attachment id (og-image / hero photo) for the link-preview card"
4115
+ ).option("--datasets <csv>", "dataset slugs the page reads").option("--writable-datasets <csv>", "dataset slugs the page may write via erdo.insertRows").option("--kv <csv>", "named KV-store slugs the page reads").option("--writable-kv <csv>", "named KV-store slugs the page may write").option("--public", "make the page publicly viewable").action(
4013
4116
  async (opts) => {
4014
4117
  try {
4015
4118
  const csv = (v) => v ? v.split(",").map((s) => s.trim()) : void 0;
@@ -4021,6 +4124,8 @@ pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/
4021
4124
  runtime: opts.runtime,
4022
4125
  meta_title: opts.metaTitle,
4023
4126
  meta_description: opts.metaDescription,
4127
+ meta_icon_asset_id: opts.metaIconAsset,
4128
+ meta_image_asset_id: opts.metaImageAsset,
4024
4129
  dataset_slugs: csv(opts.datasets),
4025
4130
  writable_dataset_slugs: csv(opts.writableDatasets),
4026
4131
  kv_slugs: csv(opts.kv),
@@ -4039,7 +4144,13 @@ pagesCmd.command("update <id>").description(
4039
4144
  ).option("--title <title>", "new title").option("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option(
4040
4145
  "--meta-title <title>",
4041
4146
  'new public SEO title (omit to keep current; pass "" to clear back to the default)'
4042
- ).option("--meta-description <description>", 'new public SEO description (omit to keep current; pass "" to clear)').option("--datasets <csv>", 'replacement read-dataset slugs ("[]" clears)').option("--writable-datasets <csv>", 'replacement writable-dataset slugs ("[]" clears)').option("--kv <csv>", 'replacement read KV-store slugs ("[]" clears)').option("--writable-kv <csv>", 'replacement writable KV-store slugs ("[]" clears)').option("--public", "make the page publicly viewable").option("--private", "make the page private").option(
4147
+ ).option("--meta-description <description>", 'new public SEO description (omit to keep current; pass "" to clear)').option(
4148
+ "--meta-icon-asset <attachmentId>",
4149
+ 'new public favicon: a Brand Style attachment id (apple-icon-180x180 / favicon-32x32) (omit to keep current; pass "" to clear back to the default)'
4150
+ ).option(
4151
+ "--meta-image-asset <attachmentId>",
4152
+ 'new public share image: a Brand Style attachment id (og-image / hero photo) (omit to keep current; pass "" to clear)'
4153
+ ).option("--datasets <csv>", 'replacement read-dataset slugs ("[]" clears)').option("--writable-datasets <csv>", 'replacement writable-dataset slugs ("[]" clears)').option("--kv <csv>", 'replacement read KV-store slugs ("[]" clears)').option("--writable-kv <csv>", 'replacement writable KV-store slugs ("[]" clears)').option("--public", "make the page publicly viewable").option("--private", "make the page private").option(
4043
4154
  "--request-review",
4044
4155
  "ask the judge lenses to review the page after this update and report findings -- for material edits to a LIVE page (layout, forms/CTA, legal/compliance copy, restructured sections), not cosmetic tweaks. First builds and full rewrites are already reviewed automatically"
4045
4156
  ).action(
@@ -4052,6 +4163,8 @@ pagesCmd.command("update <id>").description(
4052
4163
  css: readMaybeFile(opts.css),
4053
4164
  meta_title: opts.metaTitle,
4054
4165
  meta_description: opts.metaDescription,
4166
+ meta_icon_asset_id: opts.metaIconAsset,
4167
+ meta_image_asset_id: opts.metaImageAsset,
4055
4168
  dataset_slugs: grantList(opts.datasets),
4056
4169
  writable_dataset_slugs: grantList(opts.writableDatasets),
4057
4170
  kv_slugs: grantList(opts.kv),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.71.0",
3
+ "version": "0.72.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {