@cmo-so/cli 0.7.5 → 0.7.7

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/cmo.mjs +230 -19
  2. package/package.json +1 -1
package/dist/cmo.mjs CHANGED
@@ -7469,11 +7469,12 @@ function createApi({ baseUrl, session: session2, fetch: fetchImpl = globalThis.f
7469
7469
  return request(`/v1/skills?${qs}`);
7470
7470
  },
7471
7471
  get: (id) => request(`/v1/skills/${encodeURIComponent(id)}`),
7472
- /** Like or save a skill (on: false undoes it). */
7473
- react: (id, kind, on2 = true) => request(
7474
- `/v1/skills/${encodeURIComponent(id)}/react`,
7475
- { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ kind, on: on2 }) }
7476
- ),
7472
+ /** Vote on or save a skill (on: false undoes it). "like" is an upvote. */
7473
+ react: (id, kind, on2 = true) => request(`/v1/skills/${encodeURIComponent(id)}/react`, {
7474
+ method: "POST",
7475
+ headers: { "Content-Type": "application/json" },
7476
+ body: JSON.stringify({ kind, on: on2 })
7477
+ }),
7477
7478
  saved: () => request("/v1/skills/saved"),
7478
7479
  /** Suggest an open-source GitHub repo; Maggie researches it (3 a day). */
7479
7480
  submit: (repoUrl, note) => request("/v1/skills/submissions", {
@@ -8144,9 +8145,9 @@ function domainApi(api2, domain) {
8144
8145
  /** The last time posts delivered by API pull were found in the website's sitemap (gives them their address). */
8145
8146
  sitemapMatch: () => api2.request(`${base}/rewrites/sitemap-match`),
8146
8147
  /** Read the sitemap (found on its own when blank) and match API-pull posts to their live address. */
8147
- runSitemapMatch: (sitemapUrl) => post(
8148
+ runSitemapMatch: (sitemapUrl2) => post(
8148
8149
  `${base}/rewrites/sitemap-match`,
8149
- sitemapUrl ? { sitemap_url: sitemapUrl } : {}
8150
+ sitemapUrl2 ? { sitemap_url: sitemapUrl2 } : {}
8150
8151
  ),
8151
8152
  rewritePolicy: () => api2.request(`${base}/rewrite-policy`),
8152
8153
  setRewritePolicy: (p) => put(`${base}/rewrite-policy`, p),
@@ -8283,6 +8284,75 @@ function createSession({ oauth: oauth2, store, skewMs = 6e4, now = Date.now, onS
8283
8284
  };
8284
8285
  }
8285
8286
 
8287
+ // ../../packages/api-client/src/sitemaps.ts
8288
+ var SITEMAPS_MAX = 20;
8289
+ var SITEMAP_LABEL_MAX = 80;
8290
+ var SITEMAP_OWNERS = ["own", "competitor", "other"];
8291
+ var SITEMAP_USES = {
8292
+ own: ["post_ideas", "channel_ideas", "internal_links", "post_matching"],
8293
+ competitor: ["post_ideas", "channel_ideas"],
8294
+ other: ["post_ideas", "channel_ideas"]
8295
+ };
8296
+ function sitemapUrl(input) {
8297
+ let v = input.trim();
8298
+ if (!v || /^[a-z][a-z0-9+.-]*:(?!\/\/|\d)/i.test(v)) return null;
8299
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(v)) v = `https://${v}`;
8300
+ let u;
8301
+ try {
8302
+ u = new URL(v);
8303
+ } catch {
8304
+ return null;
8305
+ }
8306
+ if (u.protocol !== "http:" && u.protocol !== "https:" || u.username || u.password) return null;
8307
+ if (!u.hostname.includes(".") || /\s/.test(v)) return null;
8308
+ u.protocol = "https:";
8309
+ return u.toString();
8310
+ }
8311
+ function sitemapUses(owner, uses) {
8312
+ const allowed2 = SITEMAP_USES[owner];
8313
+ const kept = allowed2.filter((u) => uses?.includes(u));
8314
+ return kept.length ? kept : [...allowed2];
8315
+ }
8316
+ function onWebsite(url, domain) {
8317
+ let host;
8318
+ try {
8319
+ host = new URL(url).hostname.toLowerCase();
8320
+ } catch {
8321
+ return false;
8322
+ }
8323
+ const d = domain.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^www\./, "");
8324
+ return Boolean(d) && (host === d || host.endsWith(`.${d}`));
8325
+ }
8326
+ function cleanSitemap(entry, domain) {
8327
+ const url = sitemapUrl(entry.url ?? "");
8328
+ if (!url) return "The sitemap must be a web address starting with http:// or https://.";
8329
+ let owner = SITEMAP_OWNERS.includes(entry.owner) ? entry.owner : "other";
8330
+ if (owner === "own" && domain && !onWebsite(url, domain)) owner = "other";
8331
+ const label = (entry.label ?? "").trim().slice(0, SITEMAP_LABEL_MAX);
8332
+ const out = { ...entry, url, owner, label: label || null, uses: sitemapUses(owner, entry.uses) };
8333
+ if (!out.id) delete out.id;
8334
+ return out;
8335
+ }
8336
+ function checkSitemaps(list2) {
8337
+ if (list2.length > SITEMAPS_MAX) return `Up to ${SITEMAPS_MAX} sitemaps. Remove one first.`;
8338
+ const seen = /* @__PURE__ */ new Set();
8339
+ for (const s of list2) {
8340
+ const k = s.url.trim().toLowerCase();
8341
+ if (seen.has(k)) return `${s.url} is already in the list.`;
8342
+ seen.add(k);
8343
+ }
8344
+ return null;
8345
+ }
8346
+ function sitemapCheckLine(check) {
8347
+ if (!check || check.status === "unchecked") return "Checked when you save";
8348
+ if (check.status === "error") return check.error?.trim() || "Couldn't read this sitemap.";
8349
+ const n = check.url_count;
8350
+ const found = n == null ? "Read" : `Found ${n.toLocaleString("en-GB")} ${n === 1 ? "page" : "pages"}`;
8351
+ const at = check.checked_at ? new Date(check.checked_at) : null;
8352
+ const when = at && !Number.isNaN(at.getTime()) ? at.toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric" }) : null;
8353
+ return when ? `${found} \xB7 checked ${when}` : found;
8354
+ }
8355
+
8286
8356
  // ../../packages/domain/src/copy/maggie.ts
8287
8357
  var q = (s) => `\u201C${String(s ?? "").slice(0, 120)}\u201D`;
8288
8358
  var named = (title, id) => {
@@ -8334,6 +8404,10 @@ var confirmCopy = {
8334
8404
  en: (a) => `Remove the call-to-action for ${q(a.target_keyword)}.`,
8335
8405
  "zh-Hant": (a) => `\u522A\u9664 ${q(a.target_keyword)} \u7684\u884C\u52D5\u547C\u7C72\u3002`
8336
8406
  },
8407
+ internal_links_set: {
8408
+ en: (a) => Number(a.count) ? `Link each new post to ${a.count} of your latest articles.` : "Stop linking new posts to your other articles.",
8409
+ "zh-Hant": (a) => Number(a.count) ? `\u6BCF\u7BC7\u65B0\u6587\u7AE0\u9023\u7D50\u5230\u4F60\u6700\u65B0\u7684 ${a.count} \u7BC7\u6587\u7AE0\u3002` : "\u65B0\u6587\u7AE0\u4E0D\u518D\u9023\u7D50\u5230\u4F60\u7684\u5176\u4ED6\u6587\u7AE0\u3002"
8410
+ },
8337
8411
  testimonial_upsert: {
8338
8412
  en: (a) => `Save the testimonial from ${a.name}${a.use_in_posts ? " and use testimonials in posts" : ""}.`,
8339
8413
  "zh-Hant": (a) => `\u5132\u5B58 ${a.name} \u7684\u5BA2\u6236\u8A55\u50F9${a.use_in_posts ? "\uFF0C\u4E26\u5728\u6587\u7AE0\u88E1\u4F7F\u7528\u8A55\u50F9" : ""}\u3002`
@@ -8366,6 +8440,22 @@ var confirmCopy = {
8366
8440
  en: (a) => `Remove the competitor ${q(a.name)}.`,
8367
8441
  "zh-Hant": (a) => `\u522A\u9664\u7AF6\u722D\u5C0D\u624B ${q(a.name)}\u3002`
8368
8442
  },
8443
+ sitemap_upsert: {
8444
+ en: (a) => {
8445
+ const whose = { own: "your site's ", competitor: "a competitor's ", other: "another site's " }[a.owner];
8446
+ const label = a.label ? ` (${q(a.label)})` : "";
8447
+ return a.url ? `Save ${whose ?? "the "}sitemap ${a.url}${label}.` : `Change the sitemap${label}: ${keys(a).filter((k) => k !== "id").join(", ")}.`;
8448
+ },
8449
+ "zh-Hant": (a) => {
8450
+ const whose = { own: "\u81EA\u5DF1\u7DB2\u7AD9\u7684", competitor: "\u7AF6\u722D\u5C0D\u624B\u7684", other: "\u5176\u4ED6\u7DB2\u7AD9\u7684" }[a.owner];
8451
+ const label = a.label ? `\uFF08${q(a.label)}\uFF09` : "";
8452
+ return a.url ? `\u5132\u5B58${whose ?? ""} sitemap ${a.url}${label}\u3002` : `\u4FEE\u6539 sitemap${label}\uFF1A${keys(a).filter((k) => k !== "id").join("\u3001")}\u3002`;
8453
+ }
8454
+ },
8455
+ sitemap_remove: {
8456
+ en: (a) => a.url ?? a.id ? `Remove the sitemap ${a.url ?? a.id}.` : "Remove a sitemap.",
8457
+ "zh-Hant": (a) => a.url ?? a.id ? `\u522A\u9664 sitemap ${a.url ?? a.id}\u3002` : "\u522A\u9664\u4E00\u500B sitemap\u3002"
8458
+ },
8369
8459
  brand_research_update: {
8370
8460
  en: (a) => `Update your market notes: ${keys(a).join(", ") || "nothing"}.`,
8371
8461
  "zh-Hant": (a) => `\u66F4\u65B0\u5E02\u5834\u8CC7\u6599\uFF1A${keys(a).join("\u3001") || "\u6C92\u6709"}\u3002`
@@ -8582,8 +8672,8 @@ var confirmCopy = {
8582
8672
  },
8583
8673
  // ---------------------------------------------------------- Marketing skills (docs/26)
8584
8674
  skill_like: {
8585
- en: (a) => `${a.undo ? "Take back your like on" : "Like"} the skill ${q(a.skill_id)} on cmo.so.`,
8586
- "zh-Hant": (a) => `${a.undo ? "\u53D6\u6D88\u5C0D" : "\u5728 cmo.so \u8B9A"}\u6280\u80FD ${q(a.skill_id)}${a.undo ? " \u7684\u8B9A" : ""}\u3002`
8675
+ en: (a) => `${a.undo ? "Take back your upvote on" : "Upvote"} the skill ${q(a.skill_id)} on cmo.so.`,
8676
+ "zh-Hant": (a) => `${a.undo ? "\u6536\u56DE\u5C0D" : "\u5728 cmo.so \u6295\u7968\u652F\u6301"}\u6280\u80FD ${q(a.skill_id)}${a.undo ? " \u7684\u652F\u6301\u7968" : ""}\u3002`
8587
8677
  },
8588
8678
  skill_save: {
8589
8679
  en: (a) => `${a.undo ? "Remove" : "Save"} the skill ${q(a.skill_id)} ${a.undo ? "from" : "to"} your saved skills.`,
@@ -9611,6 +9701,24 @@ var brandTools = [
9611
9701
  return { ctas: out.ctas?.map((c) => c.target_keyword) };
9612
9702
  }
9613
9703
  }),
9704
+ tool({
9705
+ name: "internal_links_set",
9706
+ description: "Set how many of the website's own latest articles each new post links to (internal cross-links, picked for the topic; separate from CTAs). 0 turns it off, max 10, default 3.",
9707
+ parameters: {
9708
+ type: "object",
9709
+ properties: { count: { type: "integer", minimum: 0, maximum: 10 } },
9710
+ required: ["count"]
9711
+ },
9712
+ group: "brand",
9713
+ need: write2,
9714
+ risk: "W1",
9715
+ run: async (ctx, { count }) => {
9716
+ const n = Math.round(Number(count));
9717
+ if (!Number.isFinite(n) || n < 0 || n > 10) throw new Error("count must be 0 to 10.");
9718
+ const out = await ws(ctx).updateBrand({ internal_links: { count: n } });
9719
+ return { internal_links: out.internal_links ?? { count: n } };
9720
+ }
9721
+ }),
9614
9722
  tool({
9615
9723
  name: "testimonial_upsert",
9616
9724
  description: "Add a customer testimonial, or change the one from the same person. Only real quotes the user gives (or examples they asked for). use_in_posts turns testimonials on in posts.",
@@ -9800,6 +9908,85 @@ var brandTools = [
9800
9908
  return { found: out.found, suggestions: out.items.map(({ id, name, website }) => ({ id, name, website })) };
9801
9909
  }
9802
9910
  }),
9911
+ tool({
9912
+ name: "sitemap_list",
9913
+ description: "The sitemaps saved in the business profile: the website's own, competitors' and other sites' (industry sites, partners), each with what it is used for (post_ideas = blog post ideas, channel_ideas = social post ideas, internal_links and post_matching = own sitemaps only) and its last check (pages found or the error). Max 20.",
9914
+ parameters: none,
9915
+ group: "brand",
9916
+ need: { resource: "overview", level: "read" },
9917
+ risk: "R0",
9918
+ run: async (ctx) => {
9919
+ const list2 = (await ws(ctx).brand()).sitemaps ?? [];
9920
+ return { max: SITEMAPS_MAX, sitemaps: list2.map(sitemapResult) };
9921
+ }
9922
+ }),
9923
+ tool({
9924
+ name: "sitemap_upsert",
9925
+ description: "Add a sitemap to the business profile, or change one (found by id from sitemap_list, else by url). owner: own (the website's own; its host must be the website or a subdomain, otherwise it is saved as other), competitor, or other. uses: own may have post_ideas, channel_ideas, internal_links, post_matching; competitor/other only post_ideas, channel_ideas; leave out for all allowed. A new sitemap needs url and owner. Only sitemap addresses the user gives or confirms (never guess one). Saving checks the sitemap; an unreachable one is still saved with the error. Max 20.",
9926
+ parameters: {
9927
+ type: "object",
9928
+ properties: {
9929
+ id: { type: "string", description: "The sitemap to change (from sitemap_list)" },
9930
+ url: { type: "string", description: "The sitemap's http(s) address; with id, its new address" },
9931
+ owner: { type: "string", enum: ["own", "competitor", "other"] },
9932
+ label: { type: "string", description: "A short name, up to 80 characters" },
9933
+ uses: {
9934
+ type: "array",
9935
+ items: { type: "string", enum: ["post_ideas", "channel_ideas", "internal_links", "post_matching"] }
9936
+ }
9937
+ }
9938
+ },
9939
+ group: "brand",
9940
+ need: write2,
9941
+ risk: "W1",
9942
+ run: async (ctx, args) => {
9943
+ const list2 = (await ws(ctx).brand()).sitemaps ?? [];
9944
+ const i = findSitemap(list2, args.id, args.id ? void 0 : args.url);
9945
+ if (args.id && i < 0) throw new Error(`No sitemap with id ${args.id}. Use sitemap_list.`);
9946
+ const old = i >= 0 ? list2[i] : void 0;
9947
+ if (!old && (!args.url || !args.owner)) throw new Error("A new sitemap needs url and owner.");
9948
+ const owner = args.owner ?? old?.owner ?? "other";
9949
+ const entry = cleanSitemap(
9950
+ {
9951
+ ...old,
9952
+ url: args.url ?? old?.url ?? "",
9953
+ owner,
9954
+ label: args.label ?? old?.label ?? null,
9955
+ // A new owner without uses gets that owner's defaults.
9956
+ uses: args.uses ?? (args.owner && args.owner !== old?.owner ? [] : old?.uses ?? [])
9957
+ },
9958
+ ctx.domain ?? void 0
9959
+ );
9960
+ if (typeof entry === "string") throw new Error(entry);
9961
+ if (old && sitemapUrl(old.url) !== entry.url) delete entry.check;
9962
+ const next = old ? list2.map((x, n) => n === i ? entry : x) : [...list2, entry];
9963
+ const problem = checkSitemaps(next);
9964
+ if (problem) throw new Error(problem);
9965
+ const saved = (await ws(ctx).updateBrand({ sitemaps: next })).sitemaps ?? next;
9966
+ const mine = saved[findSitemap(saved, void 0, entry.url)];
9967
+ return {
9968
+ saved: mine ? sitemapResult(mine) : sitemapResult(entry),
9969
+ ...(mine?.owner ?? entry.owner) !== owner ? { note: "Not on this website, so it was saved as another site's sitemap (owner other)." } : {},
9970
+ sitemaps: saved.length
9971
+ };
9972
+ }
9973
+ }),
9974
+ tool({
9975
+ name: "sitemap_remove",
9976
+ description: "Remove a sitemap from the business profile (by id from sitemap_list, or by url). Maggie stops reading it; ideas already found stay.",
9977
+ parameters: { type: "object", properties: { id: { type: "string" }, url: { type: "string" } } },
9978
+ group: "brand",
9979
+ need: write2,
9980
+ risk: "W1",
9981
+ run: async (ctx, { id, url }) => {
9982
+ if (!id && !url) throw new Error("Say which sitemap: id or url.");
9983
+ const list2 = (await ws(ctx).brand()).sitemaps ?? [];
9984
+ const i = findSitemap(list2, id, url);
9985
+ if (i < 0) throw new Error(`No sitemap ${id ?? url} was found. Use sitemap_list.`);
9986
+ const saved = (await ws(ctx).updateBrand({ sitemaps: list2.filter((_, n) => n !== i) })).sitemaps ?? [];
9987
+ return { removed: list2[i]?.url, sitemaps: saved.map((s) => s.url) };
9988
+ }
9989
+ }),
9803
9990
  tool({
9804
9991
  name: "brand_research_update",
9805
9992
  description: "Change the profile's market notes: market_research (text), additional_information (anything else Maggie should know) or the SWOT list (each {name: Strengths|Weaknesses|Opportunities|Threats, description}). A list replaces the whole list.",
@@ -9935,6 +10122,22 @@ function brandGaps(b) {
9935
10122
  check("industries", b.industries, "the industry, for examples and wording");
9936
10123
  return { complete: gaps.length === 0, gaps };
9937
10124
  }
10125
+ function findSitemap(list2, id, url) {
10126
+ if (id) return list2.findIndex((s) => s.id === id);
10127
+ const u = url ? (sitemapUrl(url) ?? url).toLowerCase() : "";
10128
+ return u ? list2.findIndex((s) => (sitemapUrl(s.url) ?? s.url).toLowerCase() === u) : -1;
10129
+ }
10130
+ function sitemapResult(s) {
10131
+ return {
10132
+ id: s.id ?? null,
10133
+ url: s.url,
10134
+ owner: s.owner,
10135
+ label: s.label ?? null,
10136
+ uses: s.uses,
10137
+ check: s.check?.status ?? "unchecked",
10138
+ status: sitemapCheckLine(s.check)
10139
+ };
10140
+ }
9938
10141
 
9939
10142
  // ../../packages/tools/src/feedback.ts
9940
10143
  var account = { resource: "account", level: "read" };
@@ -10689,7 +10892,7 @@ var ID = { type: "string", description: "The skill id, like cmo-ad-creative" };
10689
10892
  var skillTools = [
10690
10893
  tool({
10691
10894
  name: "skills_search",
10692
- description: "Search cmo.so's open library of marketing skills (step-by-step methods an AI agent follows, free and MIT licensed): returns matching skills with their summary, likes and page. Use it when the user asks how to do a marketing job, wants a playbook or template, or asks which skills exist. Empty query: the most useful ones and the categories.",
10895
+ description: "Search cmo.so's open library of marketing skills (step-by-step methods an AI agent follows, free and MIT licensed): returns matching skills with their summary, vote score and page. Use it when the user asks how to do a marketing job, wants a playbook or template, or asks which skills exist. Empty query: the most useful ones and the categories.",
10693
10896
  parameters: {
10694
10897
  type: "object",
10695
10898
  properties: {
@@ -10713,7 +10916,13 @@ var skillTools = [
10713
10916
  return {
10714
10917
  total_in_library: r.total,
10715
10918
  ...a.query ? {} : { categories: r.categories.map((c) => `${c.id} (${c.count})`) },
10716
- skills: r.skills.map((s) => ({ id: s.id, name: s.name, summary: s.summary, likes: s.likes, page: s.url }))
10919
+ skills: r.skills.map((s) => ({
10920
+ id: s.id,
10921
+ name: s.name,
10922
+ summary: s.summary,
10923
+ score: s.score ?? s.likes,
10924
+ page: s.url
10925
+ }))
10717
10926
  };
10718
10927
  }
10719
10928
  }),
@@ -10732,7 +10941,7 @@ var skillTools = [
10732
10941
  name: s.name,
10733
10942
  summary: s.summary,
10734
10943
  page: s.url,
10735
- likes: s.likes,
10944
+ score: s.score ?? s.likes,
10736
10945
  saves: s.saves,
10737
10946
  uses_tools: s.mcp_tools,
10738
10947
  pairs_with: s.pairs_with,
@@ -10744,10 +10953,10 @@ var skillTools = [
10744
10953
  }),
10745
10954
  tool({
10746
10955
  name: "skill_like",
10747
- description: "Like a skill in the cmo.so library for the user (or take the like back with undo). Likes help the library decide what to keep improving. Only when the user asks.",
10956
+ description: "Upvote a skill in the cmo.so library for the user (or take the upvote back with undo). Votes help the library decide what to keep improving. Only when the user asks.",
10748
10957
  parameters: {
10749
10958
  type: "object",
10750
- properties: { skill_id: ID, undo: { type: "boolean", description: "true takes the like back" } },
10959
+ properties: { skill_id: ID, undo: { type: "boolean", description: "true takes the upvote back" } },
10751
10960
  required: ["skill_id"]
10752
10961
  },
10753
10962
  group: "account",
@@ -10756,12 +10965,12 @@ var skillTools = [
10756
10965
  idempotent: true,
10757
10966
  run: async (ctx, a) => {
10758
10967
  const r = await ctx.api.skills.react(String(a.skill_id), "like", !a.undo);
10759
- return { skill_id: r.item, liked: r.liked, likes: r.counts.likes };
10968
+ return { skill_id: r.item, upvoted: r.liked, score: r.counts.score ?? r.counts.likes };
10760
10969
  }
10761
10970
  }),
10762
10971
  tool({
10763
10972
  name: "skill_save",
10764
- description: "Save a skill to the user's saved skills on cmo.so (or remove it with undo), to find it again at cmo.so/skills/saved or with skills_saved_list.",
10973
+ description: "Save a skill to the user's saved skills on cmo.so (or remove it with undo), to find it again under Saved at cmo.so/skills or with skills_saved_list.",
10765
10974
  parameters: {
10766
10975
  type: "object",
10767
10976
  properties: { skill_id: ID, undo: { type: "boolean", description: "true removes it from saved" } },
@@ -10794,7 +11003,7 @@ var skillTools = [
10794
11003
  }),
10795
11004
  tool({
10796
11005
  name: "skill_submit",
10797
- description: "Suggest a public open-source GitHub repo for cmo.so's skill library. Maggie researches it within minutes and decides: add a new skill, improve an existing one, keep it as a candidate that needs 10 likes in 30 days, or decline (licence first: permissive licences can be adapted, copyleft only inspires, no licence is declined). 3 suggestions a day. Follow up with skill_submissions_list.",
11006
+ description: "Suggest a public open-source GitHub repo for cmo.so's skill library. Maggie researches it within minutes and decides: add a new skill, improve an existing one, keep it as a candidate that needs a vote score of 10 in 30 days, or decline (licence first: permissive licences can be adapted, copyleft only inspires, no licence is declined). 3 suggestions a day. Follow up with skill_submissions_list.",
10798
11007
  parameters: {
10799
11008
  type: "object",
10800
11009
  properties: {
@@ -15682,7 +15891,7 @@ var program = new Command();
15682
15891
  // package.json
15683
15892
  var package_default = {
15684
15893
  name: "@cmo-so/cli",
15685
- version: "0.7.5",
15894
+ version: "0.7.7",
15686
15895
  description: "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
15687
15896
  type: "module",
15688
15897
  license: "MIT",
@@ -25914,7 +26123,9 @@ var OPEN_WORLD = /* @__PURE__ */ new Set([
25914
26123
  "seo_audit_run",
25915
26124
  "ai_visibility_prompt_check",
25916
26125
  "idea_add",
25917
- "competitor_suggestions_find"
26126
+ "competitor_suggestions_find",
26127
+ // Saving makes CMO.so fetch the sitemap from its website.
26128
+ "sitemap_upsert"
25918
26129
  ]);
25919
26130
  var OVERWRITES = /* @__PURE__ */ new Set([
25920
26131
  "ai_visibility_keywords_set",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmo-so/cli",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "CMO.so on the command line: your websites, ideas, blog posts, LinkedIn queue and results, plus a local MCP server for Claude Desktop, Cursor and other MCP clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",