@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.
- package/dist/cmo.mjs +230 -19
- 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
|
-
/**
|
|
7473
|
-
react: (id, kind, on2 = true) => request(
|
|
7474
|
-
|
|
7475
|
-
|
|
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: (
|
|
8148
|
+
runSitemapMatch: (sitemapUrl2) => post(
|
|
8148
8149
|
`${base}/rewrites/sitemap-match`,
|
|
8149
|
-
|
|
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
|
|
8586
|
-
"zh-Hant": (a) => `${a.undo ? "\
|
|
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,
|
|
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) => ({
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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",
|