katman-mcp 0.2.0 → 0.3.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.
- package/README.md +48 -42
- package/dist/license.js +201 -12
- package/dist/meta.js +25 -13
- package/dist/plans.js +13 -6
- package/dist/tools/account/account.js +19 -10
- package/dist/tools/account/cli.js +4 -3
- package/dist/tools/account/index.js +1 -1
- package/dist/tools/account/upsell.js +156 -39
- package/dist/tools/crawl/audit/report.js +2 -2
- package/dist/tools/crawl/cli.js +18 -6
- package/dist/tools/crawl/index.js +39 -8
- package/dist/tools/crawl/scan/framework.js +4 -1
- package/dist/tools/method/core/lint.js +15 -1
- package/dist/tools/method/core/prompt.js +2 -1
- package/dist/tools/method/data/_store.js +17 -0
- package/dist/tools/method/data/anatomy.js +12 -141
- package/dist/tools/method/data/appendices.js +22 -202
- package/dist/tools/method/data/cloudflare.js +28 -192
- package/dist/tools/method/data/evidence.js +18 -137
- package/dist/tools/method/data/geo-jobs.js +8 -128
- package/dist/tools/method/data/landing.js +14 -67
- package/dist/tools/method/data/lessons.js +16 -127
- package/dist/tools/method/data/measurement.js +38 -142
- package/dist/tools/method/data/offsite.js +16 -148
- package/dist/tools/method/data/pages.js +16 -167
- package/dist/tools/method/data/prompts.js +8 -633
- package/dist/tools/method/data/publish.js +14 -113
- package/dist/tools/method/data/research.js +14 -131
- package/dist/tools/method/data/roadmap.js +12 -105
- package/dist/tools/method/data/traps.js +6 -74
- package/dist/tools/method/data/verify.js +12 -88
- package/dist/tools/method/data/viral-list.js +12 -39
- package/dist/tools/method/lib/prompt-lookup.js +18 -0
- package/dist/tools/method/mcp/resources.js +4 -3
- package/dist/tools/visibility/index.js +2 -2
- package/dist/tools/visibility/monitoring.js +3 -0
- package/package.json +5 -3
- package/server.json +3 -3
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// katman_account (always open, even without a key): the Katman plan behind KATMAN_KEY (none,
|
|
2
|
-
//
|
|
3
|
-
// or upgrade. Never shows the key.
|
|
1
|
+
// katman_account (always open, even without a key): the Katman plan behind KATMAN_KEY (none, Katman Pro with this
|
|
2
|
+
// month's audits and sites, or an Audit Week bought earlier with the days left), the tools it unlocks, the masked
|
|
3
|
+
// account email, the last check, and how to get a key or upgrade. Never shows the key.
|
|
4
4
|
import { OPEN_TOOLS, TOOL_FEATURES, allows, toolsOf } from "../../license.js";
|
|
5
|
-
import { APP_URL, BILLING_URL, KEYS_URL, PRICING_URL, PRO_PRICE_NOTE } from "../../meta.js";
|
|
6
|
-
import { NO_KEY_TEXT, endedPhrase, fmtDay, fmtWhen, howToText, planDaysLeft, reasonText } from "./upsell.js";
|
|
5
|
+
import { APP_URL, AUDIT_COUNT_NOTE, BILLING_URL, BUY_PRO_3_URL, KEYS_URL, PRICING_URL, PRO_PRICE_NOTE } from "../../meta.js";
|
|
6
|
+
import { NO_KEY_TEXT, endedPhrase, fmtDay, fmtWhen, howToText, planDaysLeft, proPlanName, reasonText, usageText } from "./upsell.js";
|
|
7
7
|
function planCell(s, left) {
|
|
8
8
|
if (s.state === "no_key")
|
|
9
9
|
return s.hint ? "None (KATMAN_KEY placeholder not filled)" : "None (no KATMAN_KEY)";
|
|
@@ -12,7 +12,7 @@ function planCell(s, left) {
|
|
|
12
12
|
if (s.plan === "audit")
|
|
13
13
|
return `Audit Week: ${left ?? "?"} day${left === 1 ? "" : "s"} left (ends ${fmtDay(s.planEndsAt)}; no auto-renewal)`;
|
|
14
14
|
if (s.plan === "pro")
|
|
15
|
-
return
|
|
15
|
+
return proPlanName(s.limits);
|
|
16
16
|
return `No active plan${s.planEnded ? ` (${endedPhrase(s)})` : s.auditWeekUsed ? " (Audit Week used)" : ""}`;
|
|
17
17
|
}
|
|
18
18
|
/** "on", "3 of 10 on" or "off". */
|
|
@@ -35,19 +35,25 @@ export async function runAccount(provider, opts = {}) {
|
|
|
35
35
|
["Features", s.features.length ? s.features.join(", ") : "none"],
|
|
36
36
|
["Last check", s.checkedAt ? `${fmtWhen(s.checkedAt)} (${source}${s.nextCheckAt && s.source !== "offline-grace" ? `; next check after ${fmtWhen(s.nextCheckAt)}` : ""})` : `– (${source})`],
|
|
37
37
|
];
|
|
38
|
+
if (s.state === "valid" && s.plan === "pro" && s.limits)
|
|
39
|
+
rows.splice(1, 0, ["Usage", usageText(s.limits, s.usage)]);
|
|
40
|
+
if (s.state === "valid" && s.updateMcpTo)
|
|
41
|
+
rows.push(["Update", `katman-mcp ${s.updateMcpTo} or newer is needed to count audits against this plan (audits are off until then): npx -y katman-mcp@latest, restart the MCP server, then katman_account with refresh: true`]);
|
|
38
42
|
if (s.error)
|
|
39
43
|
rows.push(["Connection", `couldn't reach katman.pro: ${s.error}`]);
|
|
40
44
|
const L = ["# Katman account", rows.map(([k, v]) => `- **${k}:** ${v}`).join("\n")];
|
|
41
45
|
L.push([
|
|
42
|
-
`**Audit
|
|
43
|
-
`**
|
|
46
|
+
`**Audit tools** (${onText(auditTools, s)}; Katman Pro): ${auditTools.join(", ")}.`,
|
|
47
|
+
`**Page-building tools** (${onText(proTools, s)}; Katman Pro): ${proTools.join(", ")}.`,
|
|
44
48
|
`**Always open:** ${OPEN_TOOLS.join(", ")}.`,
|
|
45
49
|
].join("\n"));
|
|
46
50
|
if (s.state === "valid" && s.plan === "pro") {
|
|
51
|
+
if (s.limits)
|
|
52
|
+
L.push(`${AUDIT_COUNT_NOTE}${s.limits.auditsPerMonth === 5 ? ` Katman Pro, 3 sites allows 3 sites and 30 audits a month: ${BUY_PRO_3_URL}.` : ""}`);
|
|
47
53
|
L.push(`Manage the subscription and invoices at ${BILLING_URL}.`);
|
|
48
54
|
}
|
|
49
55
|
else if (s.state === "valid" && s.plan === "audit") {
|
|
50
|
-
L.push(`Your Audit Week ends on ${fmtDay(s.planEndsAt)} and doesn't renew. Katman Pro builds the pages (the P0–P14 prompts, page briefs, file generation, IndexNow, monitoring and the dashboard sync): ${PRO_PRICE_NOTE}
|
|
56
|
+
L.push(`Your Audit Week ends on ${fmtDay(s.planEndsAt)} and doesn't renew. Katman Pro builds the pages (the P0–P14 prompts, page briefs, file generation, IndexNow, monitoring and the dashboard sync): ${PRO_PRICE_NOTE}. Upgrade at ${BILLING_URL}; the key stays the same.`);
|
|
51
57
|
}
|
|
52
58
|
else {
|
|
53
59
|
if (s.state === "no_key" && !s.hint)
|
|
@@ -56,7 +62,7 @@ export async function runAccount(provider, opts = {}) {
|
|
|
56
62
|
L.push(`**Why the tools are off:** ${reasonText(s)}`);
|
|
57
63
|
L.push(howToText(s));
|
|
58
64
|
}
|
|
59
|
-
L.push(`Network: Katman contacts katman.pro only to check this key (at most once a day and when a plan ends, only when KATMAN_KEY is set) and when you call katman_sync. The key is never shown or written to disk; the cache (~/.katman/license.json) stores its SHA-256 hash. Plans: ${PRICING_URL} · account and keys: ${APP_URL}.`);
|
|
65
|
+
L.push(`Network: Katman contacts katman.pro only to check this key (at most once a day and when a plan ends, only when KATMAN_KEY is set), to count an audit (right before each katman_audit_site run; it sends the domain being audited) and when you call katman_sync. The key is never shown or written to disk; the cache (~/.katman/license.json) stores its SHA-256 hash. Plans: ${PRICING_URL} · account and keys: ${APP_URL}.`);
|
|
60
66
|
return {
|
|
61
67
|
text: L.join("\n\n"),
|
|
62
68
|
structured: {
|
|
@@ -67,6 +73,9 @@ export async function runAccount(provider, opts = {}) {
|
|
|
67
73
|
planEnded: !!s.planEnded,
|
|
68
74
|
endedPlan: s.endedPlan ?? null,
|
|
69
75
|
auditWeekUsed: !!s.auditWeekUsed,
|
|
76
|
+
limits: s.limits ?? null,
|
|
77
|
+
usage: s.usage ?? null,
|
|
78
|
+
updateMcpTo: s.updateMcpTo ?? null,
|
|
70
79
|
features: s.features,
|
|
71
80
|
account: { emailMasked: s.emailMasked ?? null },
|
|
72
81
|
checkedAt: s.checkedAt ?? null,
|
|
@@ -16,8 +16,9 @@ export const commands = {
|
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
18
|
export const help = [
|
|
19
|
-
" katman-mcp account [--refresh] your Katman plan (none /
|
|
19
|
+
" katman-mcp account [--refresh] your Katman plan (none / Katman Pro / an older Audit Week), this month's audits and sites, and how to get a key or upgrade",
|
|
20
20
|
"",
|
|
21
|
-
"Every command except account, --help and --version needs KATMAN_KEY with an active plan:
|
|
22
|
-
"
|
|
21
|
+
"Every command except account, --help and --version needs KATMAN_KEY with an active Katman Pro plan (1 site $49/month: 5 audits a month;",
|
|
22
|
+
"3 sites $88/month: 30 audits a month; every command is included). Without it, or when this month's audits or sites are used, a command",
|
|
23
|
+
"explains why and exits with code 4. `audit` counts as one audit; every other command is not counted. Plans: https://katman.pro/pricing",
|
|
23
24
|
];
|
|
@@ -9,7 +9,7 @@ import { runSync } from "./sync.js";
|
|
|
9
9
|
export function registerAccountTools(server, ctx) {
|
|
10
10
|
server.registerTool("katman_account", {
|
|
11
11
|
title: "Katman account and plan",
|
|
12
|
-
description: "Always works, even without a key. Shows the Katman plan behind KATMAN_KEY (none, Audit Week with the days left
|
|
12
|
+
description: "Always works, even without a key. Shows the Katman plan behind KATMAN_KEY (none, Katman Pro 1 site or 3 sites with this month's audits and sites used, or an older Audit Week with the days left), the tools it unlocks, the account email (masked), when the key was last checked, and how to get a key or upgrade (the plans at katman.pro/pricing, with the exact MCP config snippet per editor). Checks the key with katman.pro at most once a day; refresh: true re-checks now (e.g. right after buying). Never shows the key.",
|
|
13
13
|
inputSchema: {
|
|
14
14
|
refresh: z.boolean().optional().describe("Re-check the key with katman.pro now instead of using today's cached answer (e.g. right after buying or upgrading)."),
|
|
15
15
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Only type imports from license.ts (license.ts imports this module); the plans come from plans.ts.
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { AUDIT_COUNT_NOTE, AUDIT_COUNT_NOTE_TR, BILLING_URL, BUY_PRO_1_URL, BUY_PRO_3_URL, KEYS_URL, KEY_DOCS_URL, PRICING_URL, PRO_3_NOTE, PRO_PRICE_NOTE, PRO_PRICE_NOTE_TR, VERSION, } from "../../meta.js";
|
|
8
8
|
import { isProOnly } from "../../plans.js";
|
|
9
9
|
import { listFiles } from "../../state.js";
|
|
10
10
|
import { domainOf, mdTable, sortFindings, truncate } from "../../util.js";
|
|
@@ -12,9 +12,10 @@ import { GATES, STEPS } from "../method/data/method.js";
|
|
|
12
12
|
import { t } from "../method/lib/l10n.js";
|
|
13
13
|
import { loadConfigSafe, loadLatestAudit, loadResearchSafe, loadScan } from "../method/lib/state.js";
|
|
14
14
|
/** Without a key: the two plans, where to get a key and where it goes. */
|
|
15
|
-
export const NO_KEY_TEXT = `Katman needs a licence key (KATMAN_KEY).
|
|
15
|
+
export const NO_KEY_TEXT = `Katman needs a licence key (KATMAN_KEY). ${PRO_PRICE_NOTE}: audit, research, plan and AI-visibility checks, the P0–P14 prompts, page briefs and file generation. Get a key at ${PRICING_URL}, then add KATMAN_KEY to your MCP config (${KEY_DOCS_URL}).`;
|
|
16
|
+
/** For an account that bought an Audit Week earlier (no longer sold): what it covers and the way up. */
|
|
16
17
|
const AUDIT_COVERS = `Your Audit Week covers audit and recommendations; Pro builds the pages. Upgrade: ${BILLING_URL}`;
|
|
17
|
-
/** Audit Week calling a Pro-only tool or command. */
|
|
18
|
+
/** An Audit Week (bought earlier) calling a Pro-only tool or command. */
|
|
18
19
|
function proToolText(name, kind) {
|
|
19
20
|
return `${name} is a Katman Pro ${kind}. ${AUDIT_COVERS}`;
|
|
20
21
|
}
|
|
@@ -119,6 +120,25 @@ function doneText(f) {
|
|
|
119
120
|
// Status texts
|
|
120
121
|
const day = (iso) => (iso ? iso.slice(0, 10) : "?");
|
|
121
122
|
const when = (iso) => (iso ? `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC` : "–");
|
|
123
|
+
/** "Katman Pro, 1 site" / "Katman Pro, 3 sites" from the plan's limits; "Katman Pro" when there are none (older plans). */
|
|
124
|
+
export function proPlanName(limits) {
|
|
125
|
+
if (limits?.sites === 1 && limits.auditsPerMonth === 5)
|
|
126
|
+
return "Katman Pro, 1 site";
|
|
127
|
+
if (limits?.sites === 3 && limits.auditsPerMonth === 30)
|
|
128
|
+
return "Katman Pro, 3 sites";
|
|
129
|
+
return "Katman Pro";
|
|
130
|
+
}
|
|
131
|
+
const countOf = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
132
|
+
/** "3 of 5 audits, 1 of 1 site used this month (counts start again 2026-11-01)"; without counts: "5 audits and 1 site a month". */
|
|
133
|
+
export function usageText(limits, usage) {
|
|
134
|
+
if (!usage)
|
|
135
|
+
return `${countOf(limits.auditsPerMonth, "audit", "audits")} and ${countOf(limits.sites, "site", "sites")} a month`;
|
|
136
|
+
return `${usage.audits} of ${countOf(limits.auditsPerMonth, "audit", "audits")}, ${usage.sites} of ${countOf(limits.sites, "site", "sites")} used this month (counts start again ${day(usage.resetsAt)})`;
|
|
137
|
+
}
|
|
138
|
+
/** The update notice for a katman-mcp too old to be counted (katman.pro's updateMcpTo). */
|
|
139
|
+
function updateText(s) {
|
|
140
|
+
return `This katman-mcp (${VERSION}) can't count audits against your plan, so the audit is off until you update to ${s.updateMcpTo} or newer: run npx -y katman-mcp@latest, restart the MCP server (or reload the editor window), then call katman_account with refresh: true.`;
|
|
141
|
+
}
|
|
122
142
|
/** Days left in a running Audit Week (7 right after it starts, 1 on its last day). */
|
|
123
143
|
export function planDaysLeft(s, now = new Date()) {
|
|
124
144
|
if (s.plan !== "audit" || !s.planEndsAt)
|
|
@@ -174,10 +194,15 @@ export function reasonText(s, o = {}) {
|
|
|
174
194
|
return endedText(s, o.lang);
|
|
175
195
|
if (s.auditWeekUsed)
|
|
176
196
|
return tr ? "Bu hesabın etkin bir planı yok: tek Denetim Haftası kullanıldı." : "This account has no active plan: its one Audit Week has been used.";
|
|
177
|
-
return tr ? "Bu hesabın henüz etkin bir planı yok
|
|
197
|
+
return tr ? "Bu hesabın henüz etkin bir Katman Pro planı yok." : "This account has no active Katman Pro plan yet.";
|
|
178
198
|
}
|
|
179
199
|
if (s.plan === "audit" && o.feature && isProOnly(o.feature))
|
|
180
200
|
return proToolText(o.name ?? "This tool", o.surface === "cli" ? "command" : "tool");
|
|
201
|
+
if (s.updateMcpTo) {
|
|
202
|
+
return tr
|
|
203
|
+
? `Bu katman-mcp (${VERSION}) planınızdaki denetimleri sayamıyor; ${s.updateMcpTo} ya da daha yeni bir sürüme güncelleyene kadar denetim kapalı: npx -y katman-mcp@latest çalıştırın, MCP sunucusunu yeniden başlatın, sonra katman_account aracını refresh: true ile çağırın.`
|
|
204
|
+
: updateText(s);
|
|
205
|
+
}
|
|
181
206
|
return tr
|
|
182
207
|
? `Planınız katman.pro’ya göre “${o.feature ?? "bu özellik"}” özelliğini içermiyor.`
|
|
183
208
|
: `Your ${s.plan === "pro" ? "Katman Pro" : "Audit Week"} plan doesn't include "${o.feature ?? "this feature"}" according to katman.pro.`;
|
|
@@ -196,14 +221,17 @@ export function statusLine(s, now = new Date()) {
|
|
|
196
221
|
return `**Katman plan:** not verified · couldn't reach katman.pro to check KATMAN_KEY (${s.error ?? "network error"}) · Katman's tools are off until a check succeeds`;
|
|
197
222
|
case "valid": {
|
|
198
223
|
const who = s.emailMasked ? ` · ${s.emailMasked}` : "";
|
|
199
|
-
if (s.plan === "pro")
|
|
200
|
-
|
|
224
|
+
if (s.plan === "pro") {
|
|
225
|
+
const used = s.limits ? ` · ${usageText(s.limits, s.usage)}` : "";
|
|
226
|
+
const update = s.updateMcpTo ? ` · update katman-mcp to ${s.updateMcpTo} (npx -y katman-mcp@latest): audits are off until then` : "";
|
|
227
|
+
return `**Katman plan:** ${proPlanName(s.limits)} · every tool on${used}${update}${who}${offlineText(s)}`;
|
|
228
|
+
}
|
|
201
229
|
if (s.plan === "audit") {
|
|
202
230
|
const left = planDaysLeft(s, now);
|
|
203
231
|
return `**Katman plan:** Audit Week · ${left === undefined ? "" : `${left} day${left === 1 ? "" : "s"} left (until ${day(s.planEndsAt)}) · `}audit and recommendation tools on; the page-building tools need Katman Pro (${BILLING_URL})${who}${offlineText(s)}`;
|
|
204
232
|
}
|
|
205
233
|
const note = s.planEnded ? ` (${endedPhrase(s)})` : s.auditWeekUsed ? " (Audit Week used)" : "";
|
|
206
|
-
const next = s.endedPlan === "pro" ? "renew Katman Pro" : onlyPro(s) ? "upgrade to Katman Pro" : "
|
|
234
|
+
const next = s.endedPlan === "pro" ? "renew Katman Pro" : onlyPro(s) ? "upgrade to Katman Pro" : "choose Katman Pro (1 site $49/month, 3 sites $88/month)";
|
|
207
235
|
return `**Katman plan:** none${note} · ${next} at ${BILLING_URL}${who}`;
|
|
208
236
|
}
|
|
209
237
|
}
|
|
@@ -220,17 +248,17 @@ function nextStepLine(s, feature) {
|
|
|
220
248
|
return "Check the internet connection, then call katman_account with refresh: true.";
|
|
221
249
|
case "valid":
|
|
222
250
|
if (s.plan === "none") {
|
|
223
|
-
return onlyPro(s)
|
|
224
|
-
? `${s.endedPlan === "pro" ? "Renew" : "Upgrade to"} Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}. Then call katman_account with refresh: true.`
|
|
225
|
-
: `Choose the Audit Week ($3 for 7 days) or Katman Pro at ${BILLING_URL}, then call katman_account with refresh: true.`;
|
|
251
|
+
return `${s.endedPlan === "pro" ? "Renew" : onlyPro(s) ? "Upgrade to" : "Choose"} Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}. Then call katman_account with refresh: true.`;
|
|
226
252
|
}
|
|
253
|
+
if (s.updateMcpTo)
|
|
254
|
+
return "Run the latest katman-mcp (npx -y katman-mcp@latest), restart the MCP server, then call katman_account with refresh: true.";
|
|
227
255
|
return "Call katman_account with refresh: true; if it persists, run the latest Katman (npx -y katman-mcp@latest).";
|
|
228
256
|
}
|
|
229
257
|
}
|
|
230
258
|
/** katman_start: the recommended next tool isn't in the plan. */
|
|
231
259
|
export function planNotice(tool, feature, s) {
|
|
232
260
|
if (s.state === "valid" && s.plan === "audit" && isProOnly(feature)) {
|
|
233
|
-
return `> **Needs Katman Pro:** ${proToolText(`\`${tool}\``, "tool")}\n>
|
|
261
|
+
return `> **Needs Katman Pro:** ${proToolText(`\`${tool}\``, "tool")}\n> ${PRO_PRICE_NOTE}. The key stays the same. After upgrading, call katman_account with refresh: true. Tell the user; the Audit Week tools keep working${s.planEndsAt ? ` until ${day(s.planEndsAt)}` : ""}.`;
|
|
234
262
|
}
|
|
235
263
|
return `> **Not in your plan:** \`${tool}\` won't run: ${reasonText(s, { feature, name: tool })} ${nextStepLine(s, feature)} Tell the user.`;
|
|
236
264
|
}
|
|
@@ -303,16 +331,13 @@ export function howToText(s, o = {}) {
|
|
|
303
331
|
case "valid":
|
|
304
332
|
if (s.plan === "none" && s.endedPlan === "pro") {
|
|
305
333
|
return tr
|
|
306
|
-
? `### Katman Pro’yu yenileyin\nAnahtarınız aynı kalır: ${BILLING_URL} adresinden yeniden abone olun (
|
|
307
|
-
: `### Renew Katman Pro\nThe key stays the same: resubscribe at ${BILLING_URL}.
|
|
334
|
+
? `### Katman Pro’yu yenileyin\nAnahtarınız aynı kalır: ${BILLING_URL} adresinden yeniden abone olun (${PRO_PRICE_NOTE_TR}). Yeniden abone oldunuz mu? ${refresh}`
|
|
335
|
+
: `### Renew Katman Pro\nThe key stays the same: resubscribe at ${BILLING_URL}. ${PRO_PRICE_NOTE}. Already renewed? ${Refresh}`;
|
|
308
336
|
}
|
|
309
|
-
if (s.plan === "none" &&
|
|
310
|
-
const why = onlyPro(s)
|
|
311
|
-
? tr ? "Her hesabın tek bir Denetim Haftası var; devam etmek için Katman Pro" : "Each account gets one Audit Week, so Katman Pro is the way to continue"
|
|
312
|
-
: tr ? "Bu araç Katman Pro’da; Denetim Haftası yalnızca denetim araçlarını kapsar" : "This tool is in Katman Pro; the Audit Week covers only the audit tools";
|
|
337
|
+
if (s.plan === "none" && onlyPro(s)) {
|
|
313
338
|
return tr
|
|
314
|
-
? `### Katman Pro’ya geçin\n${
|
|
315
|
-
: `### Upgrade to Katman Pro\n${
|
|
339
|
+
? `### Katman Pro’ya geçin\n${PRO_PRICE_NOTE_TR}: ${BILLING_URL}. Anahtar aynı kalır. Geçtiniz mi? ${refresh}`
|
|
340
|
+
: `### Upgrade to Katman Pro\n${PRO_PRICE_NOTE}. Upgrade at ${BILLING_URL}; the key stays the same. Already upgraded? ${Refresh}`;
|
|
316
341
|
}
|
|
317
342
|
if (s.plan === "none") {
|
|
318
343
|
return tr
|
|
@@ -320,7 +345,12 @@ export function howToText(s, o = {}) {
|
|
|
320
345
|
: `### Choose a plan\n${plansList("en")}\n\nBuy at ${BILLING_URL}; the key stays the same. Then ${refresh}`;
|
|
321
346
|
}
|
|
322
347
|
if (s.plan === "audit" && o.feature && isProOnly(o.feature)) {
|
|
323
|
-
return `### Upgrade to Katman Pro\
|
|
348
|
+
return `### Upgrade to Katman Pro\n${PRO_PRICE_NOTE}. Upgrade at ${BILLING_URL}; the key stays the same. Your Audit Week tools keep working${s.planEndsAt ? ` until ${day(s.planEndsAt)}` : ""}. Already upgraded? ${Refresh}`;
|
|
349
|
+
}
|
|
350
|
+
if (s.updateMcpTo) {
|
|
351
|
+
return tr
|
|
352
|
+
? `### Katman’ı güncelleyin\nBu sürüm planınızdaki denetimleri sayamıyor. En son sürümü çalıştırın (npx -y katman-mcp@latest), MCP sunucusunu yeniden başlatın ve ${refresh}`
|
|
353
|
+
: `### Update Katman\nThis version can't count audits against your plan. Run the latest version (npx -y katman-mcp@latest), restart the MCP server (or reload the editor window), then ${refresh}`;
|
|
324
354
|
}
|
|
325
355
|
return tr
|
|
326
356
|
? `### Katman’ı güncelleyin\nEn son sürümü çalıştırın (npx -y katman-mcp@latest) ve ${refresh} Sorun sürerse hello@katman.pro adresine yazın.`
|
|
@@ -331,24 +361,28 @@ export function howToText(s, o = {}) {
|
|
|
331
361
|
function plansList(lang) {
|
|
332
362
|
return lang === "tr"
|
|
333
363
|
? [
|
|
334
|
-
"- **
|
|
335
|
-
"- **Katman Pro
|
|
364
|
+
"- **Katman Pro, 1 site**: ayda 49 $. 1 site, ayda 5 denetim. Tüm araçlar: P0–P14 prompt’ları, sayfa brief’leri ve dosya üretimi dâhil.",
|
|
365
|
+
"- **Katman Pro, 3 site**: ayda 88 $. 3 site, ayda 30 denetim. Tüm araçlar, 1 siteli plandakiyle aynı.",
|
|
366
|
+
"",
|
|
367
|
+
AUDIT_COUNT_NOTE_TR,
|
|
336
368
|
].join("\n")
|
|
337
369
|
: [
|
|
338
|
-
|
|
339
|
-
|
|
370
|
+
"- **Katman Pro, 1 site**: $49/month. 1 site, 5 audits a month. Every tool, including the P0–P14 prompts, page briefs and file generation.",
|
|
371
|
+
"- **Katman Pro, 3 sites**: $88/month. 3 sites, 30 audits a month. Every tool, the same as the 1-site plan.",
|
|
372
|
+
"",
|
|
373
|
+
AUDIT_COUNT_NOTE,
|
|
340
374
|
].join("\n");
|
|
341
375
|
}
|
|
342
376
|
/** The two plans as a table (katman_start without a plan). */
|
|
343
377
|
function plansTable(lang) {
|
|
344
378
|
return lang === "tr"
|
|
345
379
|
? mdTable(["Plan", "Fiyat", "Kapsadığı"], [
|
|
346
|
-
["**
|
|
347
|
-
["**Katman Pro**", "
|
|
380
|
+
["**Katman Pro, 1 site**", "Ayda 49 $", "1 site, ayda 5 denetim. Tüm araçlar: P0–P14 prompt’ları, sayfa brief’leri, dosya üretimi (robots.txt, llms.txt, JSON-LD), IndexNow, zamanlanmış izleme ve katman.pro paneli dâhil."],
|
|
381
|
+
["**Katman Pro, 3 site**", "Ayda 88 $", "3 site, ayda 30 denetim. Tüm araçlar, 1 siteli plandakiyle aynı."],
|
|
348
382
|
])
|
|
349
383
|
: mdTable(["Plan", "Price", "What it covers"], [
|
|
350
|
-
["**
|
|
351
|
-
["**Katman Pro
|
|
384
|
+
["**Katman Pro, 1 site**", "$49/month", "1 site, 5 audits a month. Every tool, including the P0–P14 prompts, page briefs, file generation (robots.txt, llms.txt, JSON-LD), IndexNow, scheduled monitoring and the katman.pro dashboard."],
|
|
385
|
+
["**Katman Pro, 3 sites**", "$88/month", "3 sites, 30 audits a month. Every tool, the same as the 1-site plan."],
|
|
352
386
|
]);
|
|
353
387
|
}
|
|
354
388
|
function headline(name, feature, s) {
|
|
@@ -371,13 +405,12 @@ export function renderUpsell(o) {
|
|
|
371
405
|
return renderCliUpsell(o);
|
|
372
406
|
const where = o.facts?.brand ?? o.facts?.domain;
|
|
373
407
|
const done = doneText(o.facts);
|
|
374
|
-
const proOnly = isProOnly(o.feature) ? ` ${o.tool} is in Katman Pro; the Audit Week doesn't include it.` : "";
|
|
375
408
|
return [
|
|
376
409
|
`## ${headline(o.tool, o.feature, o.status)}`,
|
|
377
410
|
`**What it would do${where ? ` for ${where}` : ""}:** ${upperFirst(pitch(o.tool, o.args ?? {}, o.facts))}`,
|
|
378
411
|
...(done ? [done] : []),
|
|
379
412
|
`**Why it didn't run:** ${reasonText(o.status, { feature: o.feature, name: o.tool })}`,
|
|
380
|
-
...(o.status.state === "no_key" ? [NO_KEY_TEXT
|
|
413
|
+
...(o.status.state === "no_key" ? [NO_KEY_TEXT] : []),
|
|
381
414
|
howToText(o.status, { rerun: o.tool, feature: o.feature }),
|
|
382
415
|
"Tell the user this in your own words. Don't try to work around it (no editing Katman's code or cache, no made-up keys); katman_account always works.",
|
|
383
416
|
].join("\n\n");
|
|
@@ -392,16 +425,13 @@ function renderCliUpsell(o) {
|
|
|
392
425
|
];
|
|
393
426
|
if (s.state === "valid") {
|
|
394
427
|
if (s.plan === "audit" && isProOnly(o.feature)) {
|
|
395
|
-
L.push(`Katman Pro: ${PRO_PRICE_NOTE}
|
|
428
|
+
L.push(`Upgrade to Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}. The key stays the same. Already upgraded? Run: katman-mcp account --refresh`);
|
|
396
429
|
}
|
|
397
430
|
else if (s.plan === "none" && s.endedPlan === "pro") {
|
|
398
|
-
L.push(`Renew Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}
|
|
399
|
-
}
|
|
400
|
-
else if (s.plan === "none" && (onlyPro(s) || isProOnly(o.feature))) {
|
|
401
|
-
L.push(`Upgrade to Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}${onlyPro(s) ? " (each account gets one Audit Week)" : ""}. The key stays the same. Already upgraded? Run: katman-mcp account --refresh`);
|
|
431
|
+
L.push(`Renew Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}. The key stays the same. Already renewed? Run: katman-mcp account --refresh`);
|
|
402
432
|
}
|
|
403
433
|
else if (s.plan === "none") {
|
|
404
|
-
L.push(
|
|
434
|
+
L.push(`${onlyPro(s) ? "Upgrade to" : "Choose"} Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}. The key stays the same. Already ${onlyPro(s) ? "upgraded" : "bought"}? Run: katman-mcp account --refresh`);
|
|
405
435
|
}
|
|
406
436
|
else {
|
|
407
437
|
L.push("Run the latest version (npx -y katman-mcp@latest), then: katman-mcp account --refresh. If it persists, write to hello@katman.pro.");
|
|
@@ -418,14 +448,20 @@ function renderCliUpsell(o) {
|
|
|
418
448
|
L.push("", "Without a key: katman-mcp account, --help and --version.");
|
|
419
449
|
return L.join("\n");
|
|
420
450
|
}
|
|
451
|
+
/** The two plans on sale, for structuredContent (no secrets). */
|
|
452
|
+
const PLANS_STRUCTURED = [
|
|
453
|
+
{ id: "pro_1", name: "Katman Pro, 1 site", price: "$49/month", sites: 1, auditsPerMonth: 5, url: BUY_PRO_1_URL },
|
|
454
|
+
{ id: "pro_3", name: "Katman Pro, 3 sites", price: "$88/month", sites: 3, auditsPerMonth: 30, url: BUY_PRO_3_URL },
|
|
455
|
+
];
|
|
421
456
|
/** structuredContent of a tool that didn't run for lack of a plan (no secrets). */
|
|
422
457
|
export function upsellStructured(tool, feature, s) {
|
|
423
458
|
return {
|
|
424
459
|
licensed: false,
|
|
425
460
|
tool,
|
|
426
461
|
feature,
|
|
427
|
-
/** The
|
|
428
|
-
requiredPlan:
|
|
462
|
+
/** The plan to buy: every tool is in both Katman Pro plans (the Audit Week is no longer sold). */
|
|
463
|
+
requiredPlan: "pro",
|
|
464
|
+
plans: PLANS_STRUCTURED,
|
|
429
465
|
license: {
|
|
430
466
|
state: s.state,
|
|
431
467
|
plan: s.plan,
|
|
@@ -461,7 +497,7 @@ export function startIntro(s, lang = "en") {
|
|
|
461
497
|
L(`**4 steps:** ${steps}, then re-measure every 14 days.`, `**4 adım:** ${steps}; sonra 14 günde bir yeniden ölçüm.`),
|
|
462
498
|
`## ${heading}`,
|
|
463
499
|
...(why ? [`**${L("Why nothing ran", "Neden çalışmadı")}:** ${why}`] : []),
|
|
464
|
-
...(s.state === "unreachable" ? [] : [plansTable(lang)]),
|
|
500
|
+
...(s.state === "unreachable" ? [] : [plansTable(lang), lang === "tr" ? AUDIT_COUNT_NOTE_TR : AUDIT_COUNT_NOTE]),
|
|
465
501
|
howToText(s, { rerun: "katman_start", feature: "start", lang }),
|
|
466
502
|
"Tell the user this in your own words, then stop: nothing else in Katman runs until KATMAN_KEY has an active plan. katman_account shows the plan at any time.",
|
|
467
503
|
].join("\n\n");
|
|
@@ -475,4 +511,85 @@ export function resourcePaywall(title, feature, s, then) {
|
|
|
475
511
|
: `${reasonText(s, { feature })} ${nextStepLine(s, feature)}`;
|
|
476
512
|
return `# ${title}\n\n${why}\n\n${then}`;
|
|
477
513
|
}
|
|
514
|
+
/** The status the no-plan / rejected-key answer of the audit check stands for, so the usual upsell can explain it. */
|
|
515
|
+
function refusedStatus(o) {
|
|
516
|
+
const { use, status } = o;
|
|
517
|
+
if (use.reason === "invalid_key" || use.reason === "revoked") {
|
|
518
|
+
return { state: use.reason === "revoked" ? "revoked" : "invalid", plan: "none", features: [], source: "network", ...(status.keyHint ? { keyHint: status.keyHint } : {}) };
|
|
519
|
+
}
|
|
520
|
+
return { ...status, state: "valid", plan: "none", features: [] };
|
|
521
|
+
}
|
|
522
|
+
const onePro = (l) => l?.auditsPerMonth === 5;
|
|
523
|
+
/** Why this month's allowance doesn't cover the audit (audit_limit / site_limit): one text for the MCP tool and the CLI. */
|
|
524
|
+
function limitLines(o) {
|
|
525
|
+
const { use, domain } = o;
|
|
526
|
+
const lim = use.limits ?? undefined;
|
|
527
|
+
const usage = use.usage;
|
|
528
|
+
const resets = usage ? `Counts start again on ${day(usage.resetsAt)}.` : "Counts start again on the 1st of next month (UTC).";
|
|
529
|
+
const plan = proPlanName(lim);
|
|
530
|
+
const lines = [];
|
|
531
|
+
if (use.reason === "audit_limit") {
|
|
532
|
+
lines.push(lim
|
|
533
|
+
? `All ${lim.auditsPerMonth} audits of this month are used (${plan}), so Katman didn't audit ${domain}. ${resets}`
|
|
534
|
+
: `This month's audits are used, so Katman didn't audit ${domain}. ${resets}`);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
const left = lim && usage ? Math.max(0, lim.auditsPerMonth - usage.audits) : undefined;
|
|
538
|
+
lines.push(lim
|
|
539
|
+
? `${plan} covers ${countOf(lim.sites, "site", "sites")} a month and you already audited ${usage ? usage.sites : lim.sites} this month, so Katman didn't audit ${domain}, a new site. ${resets}`
|
|
540
|
+
: `This plan's sites for this month are used, so Katman didn't audit ${domain}. ${resets}`);
|
|
541
|
+
lines.push(`Audits of a site you already audited this month still work${left !== undefined ? ` (${countOf(left, "audit", "audits")} left)` : ""}.`);
|
|
542
|
+
}
|
|
543
|
+
if (onePro(lim))
|
|
544
|
+
lines.push(`To audit more now: upgrade to ${PRO_3_NOTE} at ${BUY_PRO_3_URL}; the key stays the same. Already upgraded? Call katman_account with refresh: true.`);
|
|
545
|
+
lines.push("Re-reading earlier results doesn't use an audit: the last audit of this project is in .katman/audit.md. Every other tool keeps working.");
|
|
546
|
+
return lines;
|
|
547
|
+
}
|
|
548
|
+
/** An audit that didn't run: this month's audits or sites are used up (explained), or the plan or key isn't valid (the usual upsell). */
|
|
549
|
+
export function renderAuditRefusal(o) {
|
|
550
|
+
const { use } = o;
|
|
551
|
+
if (use.reason !== "audit_limit" && use.reason !== "site_limit") {
|
|
552
|
+
return renderUpsell({ tool: "katman_audit_site", feature: "audit", status: refusedStatus(o), facts: o.facts, args: { site_url: o.domain }, surface: o.surface, command: o.command, example: o.example });
|
|
553
|
+
}
|
|
554
|
+
const lines = limitLines(o);
|
|
555
|
+
if (o.surface === "cli") {
|
|
556
|
+
return [`${o.command ?? "katman-mcp audit"} didn't run: ${use.reason === "audit_limit" ? "this month's audits are used" : "this month's sites are used"}.`, "", ...lines, "", "This month's counts: katman-mcp account"].join("\n");
|
|
557
|
+
}
|
|
558
|
+
return [
|
|
559
|
+
`## katman_audit_site didn't run: ${use.reason === "audit_limit" ? "this month's audits are used" : "this month's sites are used"}`,
|
|
560
|
+
lines.join("\n\n"),
|
|
561
|
+
"Tell the user this in your own words. Don't try to work around it (no editing Katman's code or cache, no made-up keys); katman_account shows this month's counts.",
|
|
562
|
+
].join("\n\n");
|
|
563
|
+
}
|
|
564
|
+
/** structuredContent of an audit that didn't run for lack of audits or sites this month (no secrets). */
|
|
565
|
+
export function auditRefusalStructured(o) {
|
|
566
|
+
const { use } = o;
|
|
567
|
+
if (use.reason !== "audit_limit" && use.reason !== "site_limit")
|
|
568
|
+
return upsellStructured("katman_audit_site", "audit", refusedStatus(o));
|
|
569
|
+
return {
|
|
570
|
+
ran: false,
|
|
571
|
+
tool: "katman_audit_site",
|
|
572
|
+
reason: use.reason,
|
|
573
|
+
domain: o.domain,
|
|
574
|
+
...(use.plan ? { plan: use.plan } : {}),
|
|
575
|
+
...(use.limits ? { limits: use.limits } : {}),
|
|
576
|
+
...(use.usage ? { usage: use.usage } : {}),
|
|
577
|
+
upgradeUrl: onePro(use.limits) ? BUY_PRO_3_URL : BILLING_URL,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/** After an audit that ran: this month's count on a plan with limits, or the note that the count couldn't be checked. */
|
|
581
|
+
export function auditCountLine(use) {
|
|
582
|
+
if (use.unchecked)
|
|
583
|
+
return `> **Audit count not checked:** couldn't reach katman.pro (${use.error ?? "no answer"}), so this audit ran without being counted.`;
|
|
584
|
+
return use.limits ? `**${proPlanName(use.limits)}:** ${usageText(use.limits, use.usage)}.` : undefined;
|
|
585
|
+
}
|
|
586
|
+
/** structuredContent.auditCount of an audit that ran. */
|
|
587
|
+
export function auditCountStructured(use) {
|
|
588
|
+
return {
|
|
589
|
+
counted: !use.unchecked,
|
|
590
|
+
...(use.unchecked ? { error: use.error ?? "no answer" } : {}),
|
|
591
|
+
...(use.limits ? { limits: use.limits } : {}),
|
|
592
|
+
...(use.usage ? { usage: use.usage } : {}),
|
|
593
|
+
};
|
|
594
|
+
}
|
|
478
595
|
export { when as fmtWhen, day as fmtDay };
|
|
@@ -21,7 +21,7 @@ export function auditMarkdown(ctx, result, findings, opts = {}) {
|
|
|
21
21
|
// Gates
|
|
22
22
|
L.push("## Gates");
|
|
23
23
|
L.push(mdTable(["Gate", "Pass", "Main reasons"], ["read", "quote", "fit"].map((k) => [
|
|
24
|
-
k === "read" ? "Read
|
|
24
|
+
k === "read" ? "Read" : k === "quote" ? "Quote" : "Fit",
|
|
25
25
|
g[k].total ? `${g[k].pass}/${g[k].total}` : "–",
|
|
26
26
|
g[k].notes.slice(1, 4).join("; ") || (g[k].total ? "all pass" : ""),
|
|
27
27
|
])));
|
|
@@ -171,7 +171,7 @@ function nineCommands(ctx) {
|
|
|
171
171
|
"3",
|
|
172
172
|
"Title, description, canonical, robots, og:image",
|
|
173
173
|
h
|
|
174
|
-
? `title ${h.title ? `${h.title.length} chars ${Y}` : N} · description ${h.description ? `${h.description.length} chars ${Y}` : N} · canonical ${canon === undefined ? N : canon ? `self ${Y}` : `→ ${h.canonical} ${N}`} · robots ${h.metaRobots ?? "none"} ${home.noindex ? N : Y} · og:image ${h.og.image ? Y : N}`
|
|
174
|
+
? `title ${h.title ? `${h.title.length} chars ${Y}` : N} · description ${h.description ? `${h.description.length} chars ${h.description.length >= 70 && h.description.length <= 160 ? Y : `${N} (aim for 70–160)`}` : N} · canonical ${canon === undefined ? N : canon ? `self ${Y}` : `→ ${h.canonical} ${N}`} · robots ${h.metaRobots ?? "none"} ${home.noindex ? N : Y} · og:image ${h.og.image ? Y : N}`
|
|
175
175
|
: "—",
|
|
176
176
|
`curl -s ${o}/ | grep -oiE '<title>[^<]*|name="description" content="[^"]*|rel="canonical" href="[^"]*|name="robots" content="[^"]*|og:image" content="[^"]*'`,
|
|
177
177
|
]);
|
package/dist/tools/crawl/cli.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseArgs } from "../../cli.js";
|
|
4
4
|
import { loadConfig, loadResearch, logEvent, readJson, saveResearch } from "../../state.js";
|
|
5
|
-
import {
|
|
5
|
+
import { EXIT_NEEDS_PLAN, defaultLicense, meterAudit, statusOf } from "../../license.js";
|
|
6
|
+
import { auditCountLine, renderAuditRefusal } from "../account/upsell.js";
|
|
7
|
+
import { AuditRefused, auditAndSave, scanAndSave } from "./index.js";
|
|
6
8
|
import { indexNowMarkdown, runIndexNow } from "./indexnow.js";
|
|
7
9
|
import { keywordsMarkdown, mergeKeywords, runKeywords, termsToSave } from "./keywords.js";
|
|
8
10
|
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
@@ -25,20 +27,25 @@ export const commands = {
|
|
|
25
27
|
return fail(`scan failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
26
28
|
}
|
|
27
29
|
},
|
|
28
|
-
async audit(args) {
|
|
30
|
+
async audit(args, deps) {
|
|
29
31
|
const a = parseArgs(args);
|
|
30
32
|
const root = path.resolve(str(a.flags.root) ?? process.cwd());
|
|
31
33
|
const max = Number(str(a.flags.max) ?? NaN);
|
|
34
|
+
const provider = deps?.license ?? defaultLicense();
|
|
32
35
|
try {
|
|
33
|
-
|
|
36
|
+
// The plan check has passed (runCli); the audit is counted right before it runs.
|
|
37
|
+
const { run, markdown, use } = await auditAndSave(root, {
|
|
34
38
|
site_url: a._[0] ?? str(a.flags.site),
|
|
35
39
|
max_urls: Number.isFinite(max) ? max : undefined,
|
|
36
40
|
urls: list(a.flags.urls),
|
|
37
41
|
include_bot_test: a.flags["no-bots"] ? false : undefined,
|
|
38
42
|
preview_urls: list(a.flags.preview),
|
|
39
|
-
}, (done, total, message) => process.stderr.write(`\r[${done}/${total}] ${message} `), !a.flags["no-save"]);
|
|
43
|
+
}, (done, total, message) => process.stderr.write(`\r[${done}/${total}] ${message} `), !a.flags["no-save"], (domain) => meterAudit(provider, domain));
|
|
40
44
|
process.stderr.write("\n");
|
|
41
45
|
console.log(markdown);
|
|
46
|
+
const count = use ? auditCountLine(use) : undefined;
|
|
47
|
+
if (count)
|
|
48
|
+
console.error(count.replace(/^> /, "").replace(/\*\*/g, ""));
|
|
42
49
|
// --strict: non-zero exit when critical findings exist (for CI / a pre-publish script, book P14)
|
|
43
50
|
if (a.flags.strict && run.findings.some((f) => f.severity === "critical"))
|
|
44
51
|
return 2;
|
|
@@ -46,6 +53,11 @@ export const commands = {
|
|
|
46
53
|
}
|
|
47
54
|
catch (e) {
|
|
48
55
|
process.stderr.write("\n");
|
|
56
|
+
if (e instanceof AuditRefused) {
|
|
57
|
+
const status = await statusOf(provider);
|
|
58
|
+
console.error(renderAuditRefusal({ use: e.use, status, domain: e.domain, surface: "cli", command: "katman-mcp audit", example: ["katman-mcp", "audit", ...args].join(" ") }));
|
|
59
|
+
return EXIT_NEEDS_PLAN;
|
|
60
|
+
}
|
|
49
61
|
return fail(`audit failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
50
62
|
}
|
|
51
63
|
},
|
|
@@ -98,9 +110,9 @@ export const commands = {
|
|
|
98
110
|
export const help = [
|
|
99
111
|
" katman-mcp scan [path] [--no-save] scan the codebase → .katman/scan.json",
|
|
100
112
|
" katman-mcp audit <url> [--max 50] [--root .] [--urls u1,u2] [--preview u1] [--no-bots] [--strict] [--no-save]",
|
|
101
|
-
" audit the live site like AI crawlers see it (--strict: exit 2 on critical)",
|
|
113
|
+
" audit the live site like AI crawlers see it (--strict: exit 2 on critical); counts as one audit of the month",
|
|
102
114
|
" katman-mcp indexnow [--root .] [--site url] [--urls u1,u2] [--submit]",
|
|
103
|
-
" IndexNow key check + new/changed URLs (dry run unless --submit
|
|
115
|
+
" IndexNow key check + new/changed URLs (dry run unless --submit)",
|
|
104
116
|
' katman-mcp keywords "<seed>" [--lang en] [--country us] [--no-expand] [--save]',
|
|
105
117
|
" Google autocomplete ideas (≤40 requests)",
|
|
106
118
|
];
|