katman-mcp 0.1.0 → 0.2.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 +43 -37
- package/dist/cli.js +27 -14
- package/dist/license.js +139 -95
- package/dist/meta.js +16 -6
- package/dist/plans.js +47 -0
- package/dist/tools/account/account.js +51 -30
- package/dist/tools/account/cli.js +4 -2
- package/dist/tools/account/index.js +7 -7
- package/dist/tools/account/sync.js +2 -2
- package/dist/tools/account/upsell.js +305 -117
- package/dist/tools/crawl/cli.js +1 -1
- package/dist/tools/crawl/index.js +16 -13
- package/dist/tools/method/core/start.js +1 -1
- package/dist/tools/method/index.js +27 -17
- package/dist/tools/method/mcp/prompts.js +6 -6
- package/dist/tools/method/mcp/resources.js +19 -16
- package/dist/tools/visibility/index.js +9 -9
- package/dist/tools/visibility/monitoring.js +3 -3
- package/package.json +1 -1
- package/server.json +4 -4
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
// Katman
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Katman plan texts: what a tool returns without the plan it needs (MCP and CLI), katman_start without a plan, the
|
|
2
|
+
// one-line plan status, the notice katman_start adds when the next tool isn't in the plan, the paywall of a gated
|
|
3
|
+
// resource and the MCP config snippets with KATMAN_KEY.
|
|
4
|
+
// Only type imports from license.ts (license.ts imports this module); the plans come from plans.ts.
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
|
-
import {
|
|
7
|
+
import { AUDIT_PRICE_NOTE, BILLING_URL, KEYS_URL, KEY_DOCS_URL, PRICING_URL, PRO_PRICE_NOTE } from "../../meta.js";
|
|
8
|
+
import { isProOnly } from "../../plans.js";
|
|
7
9
|
import { listFiles } from "../../state.js";
|
|
8
|
-
import { domainOf, sortFindings, truncate } from "../../util.js";
|
|
10
|
+
import { domainOf, mdTable, sortFindings, truncate } from "../../util.js";
|
|
11
|
+
import { GATES, STEPS } from "../method/data/method.js";
|
|
12
|
+
import { t } from "../method/lib/l10n.js";
|
|
9
13
|
import { loadConfigSafe, loadLatestAudit, loadResearchSafe, loadScan } from "../method/lib/state.js";
|
|
10
|
-
|
|
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). Audit Week — $3 for 7 days: audit, research, plan and AI-visibility checks; it doesn't build pages. Katman Pro — $29/month founding price ($290/year): everything, including 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
|
+
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
|
+
function proToolText(name, kind) {
|
|
19
|
+
return `${name} is a Katman Pro ${kind}. ${AUDIT_COVERS}`;
|
|
20
|
+
}
|
|
21
|
+
const upperFirst = (x) => x.replace(/^./, (c) => c.toUpperCase());
|
|
11
22
|
const filled = (s) => !!s && !!s.trim() && !/\[(FILL|DOLDUR)\]/i.test(s);
|
|
12
23
|
export async function projectFacts(root) {
|
|
13
24
|
if (root === path.parse(root).root || root === os.homedir())
|
|
@@ -45,21 +56,33 @@ const pick = (g) => ({ pass: g.pass, total: g.total });
|
|
|
45
56
|
function gatesText(a) {
|
|
46
57
|
return `Read ${a.gates.read.pass}/${a.gates.read.total} · Quote ${a.gates.quote.pass}/${a.gates.quote.total} · Fit ${a.gates.fit.pass}/${a.gates.fit.total} pages pass`;
|
|
47
58
|
}
|
|
48
|
-
|
|
59
|
+
/** "the code scan (…) and the live audit (…)", or "" when neither exists yet. */
|
|
60
|
+
function foundParts(f) {
|
|
49
61
|
const parts = [];
|
|
50
62
|
if (f?.scan)
|
|
51
63
|
parts.push(`the code scan (${f.scan.findings} issue${f.scan.findings === 1 ? "" : "s"}${f.scan.critical ? `, ${f.scan.critical} critical` : ""})`);
|
|
52
64
|
if (f?.audit)
|
|
53
65
|
parts.push(`the live audit (${gatesText(f.audit)})`);
|
|
54
|
-
return parts.
|
|
66
|
+
return parts.join(" and ");
|
|
55
67
|
}
|
|
56
|
-
/** One sentence: what this
|
|
68
|
+
/** One sentence: what this tool would do for this project. */
|
|
57
69
|
export function pitch(tool, args, f) {
|
|
58
70
|
const brand = f?.brand ?? f?.domain ?? "this site";
|
|
59
71
|
const str = (k) => (typeof args[k] === "string" && args[k] ? String(args[k]) : undefined);
|
|
72
|
+
const found = foundParts(f);
|
|
60
73
|
switch (tool) {
|
|
74
|
+
case "katman_start":
|
|
75
|
+
return `show where ${brand} stands in the Katman method (research, scan, audit, plan, measurements) and the exact next tool call.`;
|
|
76
|
+
case "katman_research":
|
|
77
|
+
return `build ${brand}'s research: the one sentence in 4 parts, the not-X note, 10–12 customer situations in the customers' own words, competitors with sources, proof and the 12 + 2 question set.`;
|
|
78
|
+
case "katman_scan_code":
|
|
79
|
+
return `scan ${f?.framework ? `this ${f.framework} project` : "this project"}'s code: whether pages render on the server or only in JavaScript, public vs app routes, robots/sitemap/llms.txt, head tags, JSON-LD and links, each finding with its gate and fix.`;
|
|
80
|
+
case "katman_audit_site":
|
|
81
|
+
return `audit ${str("site_url") ?? f?.domain ?? "the live site"} the way AI crawlers see it: robots.txt and CDN/firewall blocks for GPTBot, ClaudeBot and PerplexityBot, text with JavaScript off, soft 404s, canonicals, sitemap, JSON-LD and internal links, with the top 10 issues ranked.`;
|
|
82
|
+
case "katman_keywords":
|
|
83
|
+
return `expand ${str("seed") ? `“${str("seed")}”` : "a seed phrase"} with Google autocomplete into the phrases customers really type, grouped into situation, single-question and comparison page ideas.`;
|
|
61
84
|
case "katman_plan":
|
|
62
|
-
return `turn what Katman found${
|
|
85
|
+
return `turn what Katman found${found ? ` in ${found}` : ""} into ${brand}'s page plan (which situation, comparison, pricing and FAQ pages are missing) and a 30-day task list, each task with a prompt code and an acceptance check.`;
|
|
63
86
|
case "katman_prompt":
|
|
64
87
|
return `give your AI prompt ${str("id") ?? "P0–P14"} filled with ${brand}'s research (one sentence, not-X note, competitors${f?.situations ? `, ${f.situations} customer situations` : ""}), with the acceptance criterion and the exact Katman check that proves the task is done.`;
|
|
65
88
|
case "katman_page_brief":
|
|
@@ -81,92 +104,135 @@ export function pitch(tool, args, f) {
|
|
|
81
104
|
case "katman_sync":
|
|
82
105
|
return `send ${brand}'s progress (gate pass counts, visibility rates, plan progress, top issue titles; never code or content) to your katman.pro dashboard.`;
|
|
83
106
|
default:
|
|
84
|
-
return "run
|
|
107
|
+
return "run this step of the Katman method for this project.";
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
/** What .katman/ already holds from an earlier plan, so the pitch can build on it. */
|
|
111
|
+
function doneText(f) {
|
|
112
|
+
const found = foundParts(f);
|
|
113
|
+
if (!found)
|
|
114
|
+
return undefined;
|
|
115
|
+
const top = f.topIssues.length ? ` Top issues: ${f.topIssues.map((x) => `“${x}”`).join("; ")}.` : "";
|
|
116
|
+
return `**Already in .katman/:** ${found}.${top}`;
|
|
93
117
|
}
|
|
94
118
|
// ---------------------------------------------------------------------------------------------------------------
|
|
95
119
|
// Status texts
|
|
96
120
|
const day = (iso) => (iso ? iso.slice(0, 10) : "?");
|
|
97
121
|
const when = (iso) => (iso ? `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC` : "–");
|
|
98
|
-
|
|
99
|
-
|
|
122
|
+
/** Days left in a running Audit Week (7 right after it starts, 1 on its last day). */
|
|
123
|
+
export function planDaysLeft(s, now = new Date()) {
|
|
124
|
+
if (s.plan !== "audit" || !s.planEndsAt)
|
|
100
125
|
return undefined;
|
|
101
|
-
return Math.max(0, Math.ceil((Date.parse(s.
|
|
126
|
+
return Math.max(0, Math.ceil((Date.parse(s.planEndsAt) - now.getTime()) / 86_400_000));
|
|
102
127
|
}
|
|
103
128
|
function offlineText(s) {
|
|
104
129
|
return s.source === "offline-grace" ? ` · offline: using the check from ${day(s.checkedAt)}, works until ${day(s.graceUntil)}` : "";
|
|
105
130
|
}
|
|
106
|
-
/**
|
|
107
|
-
export function
|
|
131
|
+
/** "Audit Week ended 2026-10-01" (for a table cell or a status line). */
|
|
132
|
+
export function endedPhrase(s) {
|
|
133
|
+
const what = s.endedPlan === "pro" ? "Katman Pro ended" : s.endedPlan === "audit" ? "Audit Week ended" : "plan ended";
|
|
134
|
+
return `${what}${s.planEndsAt ? ` ${day(s.planEndsAt)}` : ""}`;
|
|
135
|
+
}
|
|
136
|
+
/** "Your Audit Week ended on 2026-10-01." */
|
|
137
|
+
function endedText(s, lang = "en") {
|
|
138
|
+
const on = s.planEndsAt ? day(s.planEndsAt) : undefined;
|
|
139
|
+
if (lang === "tr") {
|
|
140
|
+
const what = s.endedPlan === "pro" ? "Katman Pro aboneliğiniz" : s.endedPlan === "audit" ? "Denetim Haftanız" : "Planınız";
|
|
141
|
+
return `${what} ${on ? `${on} tarihinde ` : ""}bitti.`;
|
|
142
|
+
}
|
|
143
|
+
const what = s.endedPlan === "pro" ? "Your Katman Pro subscription" : s.endedPlan === "audit" ? "Your Audit Week" : "Your Katman plan";
|
|
144
|
+
return `${what} ${on ? `ended on ${on}` : "has ended"}.`;
|
|
145
|
+
}
|
|
146
|
+
/** The only way on after an Audit Week (each account gets one), or after Pro ended. */
|
|
147
|
+
const onlyPro = (s) => !!s.planEnded || !!s.auditWeekUsed;
|
|
148
|
+
/** Why a tool didn't run (one or two sentences). `name` is the tool, or the CLI command. */
|
|
149
|
+
export function reasonText(s, o = {}) {
|
|
150
|
+
const tr = o.lang === "tr";
|
|
108
151
|
switch (s.state) {
|
|
109
152
|
case "no_key":
|
|
110
|
-
|
|
153
|
+
if (s.hint)
|
|
154
|
+
return s.hint;
|
|
155
|
+
if (tr)
|
|
156
|
+
return "Bu MCP sunucusunun yapılandırmasında KATMAN_KEY yok.";
|
|
157
|
+
return o.surface === "cli" ? "No KATMAN_KEY is set in the environment." : "No KATMAN_KEY is set in this MCP server's configuration.";
|
|
111
158
|
case "invalid":
|
|
112
|
-
|
|
159
|
+
if (s.source === "local") {
|
|
160
|
+
return tr
|
|
161
|
+
? "KATMAN_KEY bir Katman anahtarına benzemiyor: anahtarlar km_live_ ile başlar, ardından harfler ve rakamlar gelir (tırnak, boşluk ya da satır sonu olmadan). Hiçbir yere gönderilmedi."
|
|
162
|
+
: s.hint ?? "KATMAN_KEY doesn't look like a Katman key.";
|
|
163
|
+
}
|
|
164
|
+
return tr ? `katman.pro bu KATMAN_KEY’i tanımıyor (${s.keyHint ?? "km_…"}).` : `katman.pro doesn't recognise this KATMAN_KEY (${s.keyHint ?? "km_…"}).`;
|
|
113
165
|
case "revoked":
|
|
114
|
-
return `This KATMAN_KEY (${s.keyHint ?? "km_…"}) was revoked on katman.pro.`;
|
|
166
|
+
return tr ? `Bu KATMAN_KEY (${s.keyHint ?? "km_…"}) katman.pro’da iptal edilmiş.` : `This KATMAN_KEY (${s.keyHint ?? "km_…"}) was revoked on katman.pro.`;
|
|
115
167
|
case "unreachable":
|
|
116
|
-
return
|
|
168
|
+
return tr
|
|
169
|
+
? `Katman, KATMAN_KEY’i kontrol etmek için katman.pro’ya ulaşamadı (${s.error ?? "ağ hatası"})${s.checkedAt ? `; son başarılı kontrol (${day(s.checkedAt)}) 7 günden eski` : ""}.`
|
|
170
|
+
: `Katman couldn't reach katman.pro to check KATMAN_KEY (${s.error ?? "network error"})${s.checkedAt ? `, and the last successful check (${day(s.checkedAt)}) is more than 7 days old` : ""}.`;
|
|
117
171
|
case "valid":
|
|
118
|
-
if (s.plan === "
|
|
119
|
-
|
|
120
|
-
|
|
172
|
+
if (s.plan === "none") {
|
|
173
|
+
if (s.planEnded)
|
|
174
|
+
return endedText(s, o.lang);
|
|
175
|
+
if (s.auditWeekUsed)
|
|
176
|
+
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 (Denetim Haftası ya da Katman Pro)." : "This account has no active plan yet (no Audit Week or Katman Pro).";
|
|
178
|
+
}
|
|
179
|
+
if (s.plan === "audit" && o.feature && isProOnly(o.feature))
|
|
180
|
+
return proToolText(o.name ?? "This tool", o.surface === "cli" ? "command" : "tool");
|
|
181
|
+
return tr
|
|
182
|
+
? `Planınız katman.pro’ya göre “${o.feature ?? "bu özellik"}” özelliğini içermiyor.`
|
|
183
|
+
: `Your ${s.plan === "pro" ? "Katman Pro" : "Audit Week"} plan doesn't include "${o.feature ?? "this feature"}" according to katman.pro.`;
|
|
121
184
|
}
|
|
122
185
|
}
|
|
123
|
-
/** One line for katman_start
|
|
186
|
+
/** One line for katman_start. */
|
|
124
187
|
export function statusLine(s, now = new Date()) {
|
|
125
188
|
switch (s.state) {
|
|
126
189
|
case "no_key":
|
|
127
|
-
return s.hint ? `**Katman plan:**
|
|
190
|
+
return s.hint ? `**Katman plan:** none · ${s.hint}` : `**Katman plan:** none (no KATMAN_KEY) · get a key at ${PRICING_URL}`;
|
|
128
191
|
case "invalid":
|
|
129
|
-
return `**Katman plan:**
|
|
192
|
+
return `**Katman plan:** none · KATMAN_KEY ${s.source === "local" ? "doesn't look like a Katman key" : "isn't recognised by katman.pro"} · get a key at ${KEYS_URL}`;
|
|
130
193
|
case "revoked":
|
|
131
|
-
return `**Katman plan:**
|
|
194
|
+
return `**Katman plan:** none · this KATMAN_KEY was revoked · create a new one at ${KEYS_URL}`;
|
|
132
195
|
case "unreachable":
|
|
133
|
-
return `**Katman plan:** not verified · couldn't reach katman.pro to check KATMAN_KEY (${s.error ?? "network error"}) ·
|
|
196
|
+
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`;
|
|
134
197
|
case "valid": {
|
|
135
198
|
const who = s.emailMasked ? ` · ${s.emailMasked}` : "";
|
|
136
199
|
if (s.plan === "pro")
|
|
137
|
-
return `**Katman plan:** Pro ·
|
|
138
|
-
if (s.plan === "
|
|
139
|
-
const left =
|
|
140
|
-
return `**Katman plan:**
|
|
200
|
+
return `**Katman plan:** Katman Pro · every tool on${who}${offlineText(s)}`;
|
|
201
|
+
if (s.plan === "audit") {
|
|
202
|
+
const left = planDaysLeft(s, now);
|
|
203
|
+
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)}`;
|
|
141
204
|
}
|
|
142
|
-
|
|
205
|
+
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" : "Audit Week ($3 for 7 days) or Katman Pro";
|
|
207
|
+
return `**Katman plan:** none${note} · ${next} at ${BILLING_URL}${who}`;
|
|
143
208
|
}
|
|
144
209
|
}
|
|
145
210
|
}
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
let why;
|
|
149
|
-
let action;
|
|
211
|
+
/** One line: what to do next, by license state (resources, notices). */
|
|
212
|
+
function nextStepLine(s, feature) {
|
|
150
213
|
switch (s.state) {
|
|
151
214
|
case "no_key":
|
|
152
|
-
|
|
153
|
-
action = `Start the ${TRIAL_DAYS}-day free trial (no card): ${APP_URL} → create a key → add it to the MCP config as KATMAN_KEY (katman_account shows the exact snippet for each editor)`;
|
|
154
|
-
break;
|
|
215
|
+
return `Get a key at ${PRICING_URL}, then add KATMAN_KEY to the MCP config (${KEY_DOCS_URL}).`;
|
|
155
216
|
case "invalid":
|
|
156
217
|
case "revoked":
|
|
157
|
-
|
|
158
|
-
action = `Create a new key at ${KEYS_URL} and replace KATMAN_KEY in the MCP config`;
|
|
159
|
-
break;
|
|
218
|
+
return `Create a new key at ${KEYS_URL} and replace KATMAN_KEY in the MCP config.`;
|
|
160
219
|
case "unreachable":
|
|
161
|
-
|
|
162
|
-
action = "Check the internet connection, then call katman_account with refresh: true";
|
|
163
|
-
break;
|
|
220
|
+
return "Check the internet connection, then call katman_account with refresh: true.";
|
|
164
221
|
case "valid":
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
222
|
+
if (s.plan === "none") {
|
|
223
|
+
return onlyPro(s) || (feature && isProOnly(feature))
|
|
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.`;
|
|
226
|
+
}
|
|
227
|
+
return "Call katman_account with refresh: true; if it persists, run the latest Katman (npx -y katman-mcp@latest).";
|
|
168
228
|
}
|
|
169
|
-
|
|
229
|
+
}
|
|
230
|
+
/** katman_start: the recommended next tool isn't in the plan. */
|
|
231
|
+
export function planNotice(tool, feature, s) {
|
|
232
|
+
if (s.state === "valid" && s.plan === "audit" && isProOnly(feature)) {
|
|
233
|
+
return `> **Needs Katman Pro:** ${proToolText(`\`${tool}\``, "tool")}\n> Katman Pro: ${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
|
+
}
|
|
235
|
+
return `> **Not in your plan:** \`${tool}\` won't run: ${reasonText(s, { feature, name: tool })} ${nextStepLine(s, feature)} Tell the user.`;
|
|
170
236
|
}
|
|
171
237
|
// ---------------------------------------------------------------------------------------------------------------
|
|
172
238
|
// Snippets
|
|
@@ -177,7 +243,7 @@ export function installSnippets() {
|
|
|
177
243
|
"claude mcp remove katman --scope user # only if Katman is already added",
|
|
178
244
|
"claude mcp add katman --scope user -e KATMAN_KEY=km_live_… -e OPENROUTER_API_KEY=sk-or-… -- npx -y katman-mcp",
|
|
179
245
|
"```",
|
|
180
|
-
"OPENROUTER_API_KEY is optional (only for API visibility runs). With the Katman Claude Code plugin, set its `katman_key` option instead.",
|
|
246
|
+
"OPENROUTER_API_KEY is optional (only for API visibility runs). With the Katman Claude Code plugin, set its `katman_key` option instead (`/plugin configure katman@katman`).",
|
|
181
247
|
"",
|
|
182
248
|
"**Cursor**: in `~/.cursor/mcp.json` (your user file, not a committed project file); keep any other env entries you have:",
|
|
183
249
|
"```json",
|
|
@@ -187,104 +253,226 @@ export function installSnippets() {
|
|
|
187
253
|
"**VS Code**: in `.vscode/mcp.json`; VS Code asks for the key once and keeps it in its secret storage:",
|
|
188
254
|
"```json",
|
|
189
255
|
"{",
|
|
190
|
-
' "inputs": [{ "type": "promptString", "id": "katman_key", "description": "Katman key (katman.pro/app)", "password": true }],',
|
|
256
|
+
' "inputs": [{ "type": "promptString", "id": "katman_key", "description": "Katman key (katman.pro/app/mcp)", "password": true }],',
|
|
191
257
|
' "servers": { "katman": { "type": "stdio", "command": "npx", "args": ["-y", "katman-mcp"], "env": { "KATMAN_KEY": "${input:katman_key}" } } }',
|
|
192
258
|
"}",
|
|
193
259
|
"```",
|
|
194
260
|
].join("\n");
|
|
195
261
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const again = rerun ? ` and run ${rerun} again` : "";
|
|
262
|
+
/** What to do next, by license state. `rerun` is the tool to call again afterwards. */
|
|
263
|
+
export function howToText(s, o = {}) {
|
|
264
|
+
const tr = o.lang === "tr";
|
|
265
|
+
const again = o.rerun ? (tr ? `, sonra ${o.rerun} aracını yeniden çağırın` : ` and run ${o.rerun} again`) : "";
|
|
266
|
+
const restart = tr
|
|
267
|
+
? `Anahtarı okuması için Katman MCP sunucusunu yeniden başlatın (ya da editör penceresini yeniden yükleyin), katman_account ile kontrol edin${again}.`
|
|
268
|
+
: `Restart the Katman MCP server (or reload the editor window) so it reads the key, then call katman_account to confirm${again}.`;
|
|
269
|
+
const refresh = tr ? `katman_account aracını refresh: true ile çağırın${again}.` : `call katman_account with refresh: true${again}.`;
|
|
270
|
+
const Refresh = upperFirst(refresh);
|
|
200
271
|
switch (s.state) {
|
|
201
272
|
case "no_key":
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
273
|
+
return (tr
|
|
274
|
+
? [
|
|
275
|
+
"### Anahtar alın",
|
|
276
|
+
`1. ${PRICING_URL} adresinden bir plan seçin, sonra ${KEYS_URL} → **Anahtar oluştur** ile anahtarınızı oluşturup kopyalayın (km_live_…; yalnızca bir kez gösterilir).`,
|
|
277
|
+
`2. Anahtarı MCP yapılandırmasına \`KATMAN_KEY\` olarak ekleyin (her editör için adım adım: ${KEY_DOCS_URL}):`,
|
|
278
|
+
]
|
|
279
|
+
: [
|
|
280
|
+
"### Get a key",
|
|
281
|
+
`1. Choose a plan at ${PRICING_URL}, then open ${KEYS_URL} → **Create key** and copy the key (km_live_…; it's shown once).`,
|
|
282
|
+
`2. Add it to the MCP config as \`KATMAN_KEY\` (step by step for each editor: ${KEY_DOCS_URL}):`,
|
|
283
|
+
]).concat(["", installSnippets(), "", `3. ${restart}`]).join("\n");
|
|
213
284
|
case "invalid":
|
|
214
285
|
case "revoked":
|
|
215
|
-
return
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
286
|
+
return (tr
|
|
287
|
+
? [
|
|
288
|
+
"### Geçerli bir anahtar kullanın",
|
|
289
|
+
`1. ${KEYS_URL} adresini açın. ${s.state === "revoked" ? "Yeni bir anahtar oluşturun" : "Anahtarı yeniden kopyalayın (km_live_… dizesinin tamamı, tırnak ya da boşluk olmadan) ya da elinizde yoksa yeni bir tane oluşturun"}.`,
|
|
290
|
+
"2. MCP yapılandırmasındaki `KATMAN_KEY` değerini değiştirin:",
|
|
291
|
+
]
|
|
292
|
+
: [
|
|
293
|
+
"### Use a valid key",
|
|
294
|
+
`1. Open ${KEYS_URL}. ${s.state === "revoked" ? "Create a new key" : "Copy the key again (the whole km_live_… string, no quotes or spaces), or create a new one if you no longer have it"}.`,
|
|
295
|
+
"2. Replace `KATMAN_KEY` in the MCP config:",
|
|
296
|
+
]).concat(["", installSnippets(), "", `3. ${restart}`]).join("\n");
|
|
297
|
+
case "unreachable": {
|
|
298
|
+
const api = /KATMAN_API_URL/.test(s.error ?? "") ? ", KATMAN_API_URL" : "";
|
|
299
|
+
return tr
|
|
300
|
+
? `### Bağlantıyı kontrol edin\nKatman, katman.pro ile başarılı bir kontrolden sonra 7 gün çevrimdışı çalışır (bir Denetim Haftasının bitişini aşmadan). Bu bilgisayardan katman.pro’ya ulaşılabildiğinden emin olun (internet bağlantısı, proxy ya da güvenlik duvarı${api}), sonra ${refresh}`
|
|
301
|
+
: `### Check the connection\nKatman keeps working offline for 7 days after a successful check with katman.pro (never past the end of an Audit Week). Make sure katman.pro is reachable from this machine (internet connection, proxy or firewall${api}), then ${refresh}`;
|
|
302
|
+
}
|
|
230
303
|
case "valid":
|
|
231
|
-
if (s.plan === "
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
304
|
+
if (s.plan === "none" && s.endedPlan === "pro") {
|
|
305
|
+
return tr
|
|
306
|
+
? `### Katman Pro’yu yenileyin\nAnahtarınız aynı kalır: ${BILLING_URL} adresinden yeniden abone olun (kurucu fiyatıyla ayda 29 $ ya da yılda 290 $). Yeniden abone oldunuz mu? ${refresh}`
|
|
307
|
+
: `### Renew Katman Pro\nThe key stays the same: resubscribe at ${BILLING_URL}. Katman Pro: ${PRO_PRICE_NOTE}. Already renewed? ${Refresh}`;
|
|
308
|
+
}
|
|
309
|
+
if (s.plan === "none" && (onlyPro(s) || (o.feature && isProOnly(o.feature)))) {
|
|
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";
|
|
313
|
+
return tr
|
|
314
|
+
? `### Katman Pro’ya geçin\n${why}: kurucu fiyatıyla ayda 29 $ (ilk 100 müşteri; abonelik sürdükçe değişmez, sonrasında 49 $) ya da yılda 290 $, ${BILLING_URL}. Anahtar aynı kalır. Geçtiniz mi? ${refresh}`
|
|
315
|
+
: `### Upgrade to Katman Pro\n${why}: ${PRO_PRICE_NOTE}, at ${BILLING_URL}. The key stays the same. Already upgraded? ${Refresh}`;
|
|
237
316
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
317
|
+
if (s.plan === "none") {
|
|
318
|
+
return tr
|
|
319
|
+
? `### Bir plan seçin\n${plansList("tr")}\n\nSatın alma: ${BILLING_URL}; anahtar aynı kalır. Satın aldıktan sonra ${refresh}`
|
|
320
|
+
: `### Choose a plan\n${plansList("en")}\n\nBuy at ${BILLING_URL}; the key stays the same. Then ${refresh}`;
|
|
321
|
+
}
|
|
322
|
+
if (s.plan === "audit" && o.feature && isProOnly(o.feature)) {
|
|
323
|
+
return `### Upgrade to Katman Pro\nKatman Pro: ${PRO_PRICE_NOTE}; the key stays the same. Your Audit Week tools keep working${s.planEndsAt ? ` until ${day(s.planEndsAt)}` : ""}. Already upgraded? ${Refresh}`;
|
|
324
|
+
}
|
|
325
|
+
return tr
|
|
326
|
+
? `### 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.`
|
|
327
|
+
: `### Update Katman\nRun the latest version (npx -y katman-mcp@latest) and ${refresh} If it persists, write to hello@katman.pro.`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/** The two plans as a short list. */
|
|
331
|
+
function plansList(lang) {
|
|
332
|
+
return lang === "tr"
|
|
333
|
+
? [
|
|
334
|
+
"- **Denetim Haftası**: 7 gün için 3 $, tek seferlik (kendiliğinden yenilenmez, hesap başına bir kez). Denetim, araştırma, plan ve yapay zekâ görünürlük ölçümleri; sayfa yapmaz, site dosyası yazmaz.",
|
|
335
|
+
"- **Katman Pro**: kurucu fiyatıyla ayda 29 $ (ilk 100 müşteri; abonelik sürdükçe değişmez, sonrasında 49 $) ya da yılda 290 $. Her şey: P0–P14 prompt’ları, sayfa brief’leri ve dosya üretimi dâhil.",
|
|
336
|
+
].join("\n")
|
|
337
|
+
: [
|
|
338
|
+
`- **Audit Week**: ${AUDIT_PRICE_NOTE}. Audit, research, plan and AI-visibility checks; it doesn't build pages or write site files.`,
|
|
339
|
+
`- **Katman Pro**: ${PRO_PRICE_NOTE}. Everything, including the P0–P14 prompts, page briefs and file generation.`,
|
|
340
|
+
].join("\n");
|
|
341
|
+
}
|
|
342
|
+
/** The two plans as a table (katman_start without a plan). */
|
|
343
|
+
function plansTable(lang) {
|
|
344
|
+
return lang === "tr"
|
|
345
|
+
? mdTable(["Plan", "Fiyat", "Kapsadığı"], [
|
|
346
|
+
["**Denetim Haftası**", "7 gün için 3 $, tek seferlik (kendiliğinden yenilenmez, hesap başına bir kez)", "Denetim ve öneriler: katman_start, araştırma, kod taraması, canlı site denetimi, anahtar kelimeler, plan, içerik kontrolü, yapay zekâ görünürlük ölçümleri, elle kontroller ve rapor. Sayfa yapmaz, site dosyası yazmaz."],
|
|
347
|
+
["**Katman Pro**", "Kurucu fiyatıyla ayda 29 $ (ilk 100 müşteri; abonelik sürdükçe değişmez, sonrasında 49 $) ya da yılda 290 $", "Her şey: P0–P14 prompt’ları, sayfa brief’leri, dosya üretimi (robots.txt, llms.txt, JSON-LD), IndexNow, zamanlanmış izleme ve katman.pro paneli dâhil."],
|
|
348
|
+
])
|
|
349
|
+
: mdTable(["Plan", "Price", "What it covers"], [
|
|
350
|
+
["**Audit Week**", AUDIT_PRICE_NOTE, "Audit and recommendations: katman_start, research, code scan, live audit, keywords, plan, content lint, AI-visibility runs, manual checks and the report. It doesn't build pages or write site files."],
|
|
351
|
+
["**Katman Pro**", PRO_PRICE_NOTE, "Everything, including the P0–P14 prompts, page briefs, file generation (robots.txt, llms.txt, JSON-LD), IndexNow, scheduled monitoring and the katman.pro dashboard."],
|
|
352
|
+
]);
|
|
353
|
+
}
|
|
354
|
+
function headline(name, feature, s) {
|
|
355
|
+
switch (s.state) {
|
|
356
|
+
case "no_key":
|
|
357
|
+
return `${name} needs a Katman licence key`;
|
|
358
|
+
case "invalid":
|
|
359
|
+
case "revoked":
|
|
360
|
+
return `${name} needs a valid Katman key`;
|
|
361
|
+
case "unreachable":
|
|
362
|
+
return `${name} couldn't check the Katman licence`;
|
|
363
|
+
case "valid":
|
|
364
|
+
if (s.plan === "none")
|
|
365
|
+
return `${name} needs an active Katman plan`;
|
|
366
|
+
return s.plan === "audit" && isProOnly(feature) ? `${name} needs Katman Pro` : `${name} isn't in your Katman plan`;
|
|
242
367
|
}
|
|
243
368
|
}
|
|
244
369
|
export function renderUpsell(o) {
|
|
245
370
|
if (o.surface === "cli")
|
|
246
371
|
return renderCliUpsell(o);
|
|
247
372
|
const where = o.facts?.brand ?? o.facts?.domain;
|
|
373
|
+
const done = doneText(o.facts);
|
|
374
|
+
const proOnly = isProOnly(o.feature) ? ` ${o.tool} is in Katman Pro; the Audit Week doesn't include it.` : "";
|
|
248
375
|
return [
|
|
249
|
-
`## ${o.tool
|
|
250
|
-
`**What it would do${where ? ` for ${where}` : ""}:** ${pitch(o.tool, o.args ?? {}, o.facts)
|
|
251
|
-
|
|
252
|
-
`**Why it didn't run:** ${reasonText(o.status, o.feature)}`,
|
|
253
|
-
|
|
254
|
-
|
|
376
|
+
`## ${headline(o.tool, o.feature, o.status)}`,
|
|
377
|
+
`**What it would do${where ? ` for ${where}` : ""}:** ${upperFirst(pitch(o.tool, o.args ?? {}, o.facts))}`,
|
|
378
|
+
...(done ? [done] : []),
|
|
379
|
+
`**Why it didn't run:** ${reasonText(o.status, { feature: o.feature, name: o.tool })}`,
|
|
380
|
+
...(o.status.state === "no_key" ? [NO_KEY_TEXT + proOnly] : []),
|
|
381
|
+
howToText(o.status, { rerun: o.tool, feature: o.feature }),
|
|
382
|
+
"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.",
|
|
255
383
|
].join("\n\n");
|
|
256
384
|
}
|
|
257
385
|
function renderCliUpsell(o) {
|
|
258
386
|
const cmd = o.command ?? o.tool;
|
|
387
|
+
const s = o.status;
|
|
259
388
|
const L = [
|
|
260
|
-
`${cmd
|
|
261
|
-
`Why it didn't run: ${reasonText(
|
|
389
|
+
`${headline(cmd, o.feature, s)}: it would ${pitch(o.tool, o.args ?? {}, o.facts)}`,
|
|
390
|
+
`Why it didn't run: ${reasonText(s, { feature: o.feature, name: cmd, surface: "cli" })}`,
|
|
262
391
|
"",
|
|
263
392
|
];
|
|
264
|
-
if (
|
|
265
|
-
|
|
393
|
+
if (s.state === "valid") {
|
|
394
|
+
if (s.plan === "audit" && isProOnly(o.feature)) {
|
|
395
|
+
L.push(`Katman Pro: ${PRO_PRICE_NOTE}; the key stays the same. Already upgraded? Run: katman-mcp account --refresh`);
|
|
396
|
+
}
|
|
397
|
+
else if (s.plan === "none" && s.endedPlan === "pro") {
|
|
398
|
+
L.push(`Renew Katman Pro at ${BILLING_URL}: ${PRO_PRICE_NOTE}; the key stays the same. Already renewed? Run: katman-mcp account --refresh`);
|
|
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`);
|
|
402
|
+
}
|
|
403
|
+
else if (s.plan === "none") {
|
|
404
|
+
L.push(`Choose a plan at ${BILLING_URL}. Audit Week: ${AUDIT_PRICE_NOTE}. Katman Pro: ${PRO_PRICE_NOTE}. The key stays the same. Already bought? Run: katman-mcp account --refresh`);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
L.push("Run the latest version (npx -y katman-mcp@latest), then: katman-mcp account --refresh. If it persists, write to hello@katman.pro.");
|
|
408
|
+
}
|
|
266
409
|
}
|
|
267
|
-
else if (
|
|
268
|
-
L.push("
|
|
410
|
+
else if (s.state === "unreachable") {
|
|
411
|
+
L.push("Katman keeps working offline for 7 days after a successful check (never past the end of an Audit Week). Make sure katman.pro is reachable, then run: katman-mcp account --refresh");
|
|
269
412
|
}
|
|
270
413
|
else {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
414
|
+
if (s.state === "no_key")
|
|
415
|
+
L.push(NO_KEY_TEXT, "");
|
|
416
|
+
L.push(`1. ${s.state === "no_key" ? `Get a key: ${PRICING_URL}, then ${KEYS_URL} → Create key` : `Get a valid key: ${KEYS_URL}`}`, "2. Run with the key in the environment:", ` KATMAN_KEY=km_live_… npx -y ${o.example ?? cmd}`, " GitHub Actions: add the repository secret KATMAN_KEY (gh secret set KATMAN_KEY) and pass it as env.");
|
|
274
417
|
}
|
|
275
|
-
L.push("", "
|
|
418
|
+
L.push("", "Without a key: katman-mcp account, --help and --version.");
|
|
276
419
|
return L.join("\n");
|
|
277
420
|
}
|
|
278
|
-
/** structuredContent of
|
|
421
|
+
/** structuredContent of a tool that didn't run for lack of a plan (no secrets). */
|
|
279
422
|
export function upsellStructured(tool, feature, s) {
|
|
280
423
|
return {
|
|
281
424
|
licensed: false,
|
|
282
425
|
tool,
|
|
283
426
|
feature,
|
|
284
|
-
|
|
285
|
-
|
|
427
|
+
/** The cheapest plan that includes the feature. */
|
|
428
|
+
requiredPlan: isProOnly(feature) ? "pro" : "audit",
|
|
429
|
+
license: {
|
|
430
|
+
state: s.state,
|
|
431
|
+
plan: s.plan,
|
|
432
|
+
...(s.planEndsAt ? { planEndsAt: s.planEndsAt } : {}),
|
|
433
|
+
...(s.planEnded ? { planEnded: true } : {}),
|
|
434
|
+
...(s.auditWeekUsed ? { auditWeekUsed: true } : {}),
|
|
435
|
+
},
|
|
436
|
+
pricingUrl: PRICING_URL,
|
|
286
437
|
keysUrl: KEYS_URL,
|
|
287
438
|
upgradeUrl: BILLING_URL,
|
|
439
|
+
docsUrl: KEY_DOCS_URL,
|
|
288
440
|
};
|
|
289
441
|
}
|
|
442
|
+
// ---------------------------------------------------------------------------------------------------------------
|
|
443
|
+
// katman_start without a plan, gated resources
|
|
444
|
+
/** katman_start without a plan: what Katman does, the two plans and how to set the key. No method walk-through. */
|
|
445
|
+
export function startIntro(s, lang = "en") {
|
|
446
|
+
const L = (en, tr) => (lang === "tr" ? tr : en);
|
|
447
|
+
const gates = GATES.map((g) => `- **${t(g.name, lang)}**: ${t(g.summary, lang)}`).join("\n");
|
|
448
|
+
const steps = STEPS.map((x) => `${x.n} ${t(x.name, lang)}`).join(" → ");
|
|
449
|
+
const heading = s.state === "no_key"
|
|
450
|
+
? L("Katman needs a licence key", "Katman bir lisans anahtarı ister")
|
|
451
|
+
: s.state === "invalid" || s.state === "revoked"
|
|
452
|
+
? L("Katman needs a valid key", "Katman geçerli bir anahtar ister")
|
|
453
|
+
: s.state === "unreachable"
|
|
454
|
+
? L("Katman couldn't check your licence", "Katman lisansınızı kontrol edemedi")
|
|
455
|
+
: L("Katman needs an active plan", "Katman etkin bir plan ister");
|
|
456
|
+
const why = s.state === "no_key" && !s.hint ? undefined : reasonText(s, { feature: "start", name: "katman_start", lang });
|
|
457
|
+
return [
|
|
458
|
+
"# Katman",
|
|
459
|
+
L("Katman makes your public website readable, quotable and recommendable by AI assistants (ChatGPT, Claude, Gemini, Perplexity, Copilot) and findable in Google, Bing and Brave. It reads your code and your live site the way AI crawlers do, tells your coding agent what to fix and which pages to write, and checks that it was done.", "Katman, herkese açık web sitenizi yapay zekâ asistanlarının (ChatGPT, Claude, Gemini, Perplexity, Copilot) okuyabileceği, alıntılayabileceği ve önerebileceği hâle getirir; Google, Bing ve Brave’de de bulunur kılar. Kodunuzu ve yayındaki sitenizi yapay zekâ tarayıcılarının gördüğü gibi okur, kod aracınıza neyi düzelteceğini ve hangi sayfaları yazacağını söyler, yapılıp yapılmadığını kontrol eder."),
|
|
460
|
+
`${L("**3 gates** every public page must pass:", "Her herkese açık sayfanın geçmesi gereken **3 kapı**:")}\n${gates}`,
|
|
461
|
+
L(`**4 steps:** ${steps}, then re-measure every 14 days.`, `**4 adım:** ${steps}; sonra 14 günde bir yeniden ölçüm.`),
|
|
462
|
+
`## ${heading}`,
|
|
463
|
+
...(why ? [`**${L("Why nothing ran", "Neden çalışmadı")}:** ${why}`] : []),
|
|
464
|
+
...(s.state === "unreachable" ? [] : [plansTable(lang)]),
|
|
465
|
+
howToText(s, { rerun: "katman_start", feature: "start", lang }),
|
|
466
|
+
"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
|
+
].join("\n\n");
|
|
468
|
+
}
|
|
469
|
+
/** A gated resource without the plan: its title, why, and what to do. */
|
|
470
|
+
export function resourcePaywall(title, feature, s, then) {
|
|
471
|
+
const why = s.state === "no_key" && !s.hint
|
|
472
|
+
? NO_KEY_TEXT
|
|
473
|
+
: s.state === "valid" && s.plan === "audit" && isProOnly(feature)
|
|
474
|
+
? `This is part of Katman Pro. ${AUDIT_COVERS}`
|
|
475
|
+
: `${reasonText(s, { feature })} ${nextStepLine(s, feature)}`;
|
|
476
|
+
return `# ${title}\n\n${why}\n\n${then}`;
|
|
477
|
+
}
|
|
290
478
|
export { when as fmtWhen, day as fmtDay };
|
package/dist/tools/crawl/cli.js
CHANGED
|
@@ -100,7 +100,7 @@ export const help = [
|
|
|
100
100
|
" katman-mcp audit <url> [--max 50] [--root .] [--urls u1,u2] [--preview u1] [--no-bots] [--strict] [--no-save]",
|
|
101
101
|
" audit the live site like AI crawlers see it (--strict: exit 2 on critical)",
|
|
102
102
|
" katman-mcp indexnow [--root .] [--site url] [--urls u1,u2] [--submit]",
|
|
103
|
-
" IndexNow key check + new/changed URLs (dry run unless --submit)",
|
|
103
|
+
" IndexNow key check + new/changed URLs (dry run unless --submit; Katman Pro)",
|
|
104
104
|
' katman-mcp keywords "<seed>" [--lang en] [--country us] [--no-expand] [--save]',
|
|
105
105
|
" Google autocomplete ideas (≤40 requests)",
|
|
106
106
|
];
|