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.
Files changed (38) hide show
  1. package/README.md +48 -42
  2. package/dist/license.js +201 -12
  3. package/dist/meta.js +25 -13
  4. package/dist/plans.js +13 -6
  5. package/dist/tools/account/account.js +19 -10
  6. package/dist/tools/account/cli.js +4 -3
  7. package/dist/tools/account/index.js +1 -1
  8. package/dist/tools/account/upsell.js +156 -39
  9. package/dist/tools/crawl/audit/report.js +2 -2
  10. package/dist/tools/crawl/cli.js +18 -6
  11. package/dist/tools/crawl/index.js +39 -8
  12. package/dist/tools/crawl/scan/framework.js +4 -1
  13. package/dist/tools/method/core/lint.js +15 -1
  14. package/dist/tools/method/core/prompt.js +2 -1
  15. package/dist/tools/method/data/_store.js +17 -0
  16. package/dist/tools/method/data/anatomy.js +12 -141
  17. package/dist/tools/method/data/appendices.js +22 -202
  18. package/dist/tools/method/data/cloudflare.js +28 -192
  19. package/dist/tools/method/data/evidence.js +18 -137
  20. package/dist/tools/method/data/geo-jobs.js +8 -128
  21. package/dist/tools/method/data/landing.js +14 -67
  22. package/dist/tools/method/data/lessons.js +16 -127
  23. package/dist/tools/method/data/measurement.js +38 -142
  24. package/dist/tools/method/data/offsite.js +16 -148
  25. package/dist/tools/method/data/pages.js +16 -167
  26. package/dist/tools/method/data/prompts.js +8 -633
  27. package/dist/tools/method/data/publish.js +14 -113
  28. package/dist/tools/method/data/research.js +14 -131
  29. package/dist/tools/method/data/roadmap.js +12 -105
  30. package/dist/tools/method/data/traps.js +6 -74
  31. package/dist/tools/method/data/verify.js +12 -88
  32. package/dist/tools/method/data/viral-list.js +12 -39
  33. package/dist/tools/method/lib/prompt-lookup.js +18 -0
  34. package/dist/tools/method/mcp/resources.js +4 -3
  35. package/dist/tools/visibility/index.js +2 -2
  36. package/dist/tools/visibility/monitoring.js +3 -0
  37. package/package.json +5 -3
  38. package/server.json +3 -3
package/README.md CHANGED
@@ -11,14 +11,20 @@ Katman is an MCP server that works inside your AI coding tool (Cursor, Claude Co
11
11
 
12
12
  ## Pricing
13
13
 
14
- Katman needs a licence key (`KATMAN_KEY`) with an active plan. Every tool except `katman_account` needs it.
14
+ Katman needs a licence key (`KATMAN_KEY`) with an active plan. Every tool except `katman_account` needs it. Katman Pro comes in two sizes, monthly, and both include every tool:
15
15
 
16
- | Plan | Price | What you get |
16
+ | Plan | Price | Each calendar month |
17
17
  |---|---|---|
18
- | **Audit Week** | **$3 once** for 7 days. No auto-renewal, one per account | Audit and recommendations: `katman_start`, `katman_research`, `katman_scan_code`, `katman_audit_site`, `katman_keywords`, `katman_plan`, `katman_lint_content`, `katman_visibility_run`, `katman_manual_check`, `katman_report`. Katman shows what keeps assistants from reading, quoting and recommending your site, and what to do about it. It doesn't build pages or write site files. |
19
- | **Katman Pro** | **$29/month founding price** for the first 100 customers, locked while you stay subscribed (later $49/month), or **$290/year** | Everything in the Audit Week, plus the tools that build: prompts P0–P14 with acceptance checks (`katman_prompt` and the `katman://prompts/*` resources), `katman_page_brief`, `katman_generate`, `katman_indexnow`, `katman_setup_monitoring` and the katman.pro dashboard sync (`katman_sync`). |
18
+ | **Katman Pro, 1 site** | **$49/month** | 1 site, 5 audits |
19
+ | **Katman Pro, 3 sites** | **$88/month** | 3 sites, 30 audits |
20
20
 
21
- Choose a plan at https://katman.pro/pricing, then sign in at https://katman.pro/app → **MCP → Create key** and add the key to your MCP config (below). Without a key, or with a plan that doesn't cover a tool, the tool tells you what it would do for your project and how to get the right plan, instead of running. `katman_account` always works and shows your plan, the days left and how to upgrade.
21
+ Every tool is in both plans, and so is the full course and the private community: `katman_start`, `katman_research`, `katman_scan_code`, `katman_audit_site`, `katman_keywords`, `katman_plan`, `katman_lint_content`, `katman_visibility_run`, `katman_manual_check`, `katman_report`, the prompts P0–P14 with acceptance checks (`katman_prompt` and the `katman://prompts/*` resources), `katman_page_brief`, `katman_generate`, `katman_indexnow`, `katman_setup_monitoring` and the katman.pro dashboard sync (`katman_sync`).
22
+
23
+ **What counts.** An audit is one run of `katman_audit_site` on a live site (or the CLI `audit`). Sites and audits are counted per calendar month (UTC): a site is a domain you audited that month. Re-reading earlier results, the other tools and visibility runs are not counted. Counts start again on the 1st. When the month's audits or sites are used up, `katman_audit_site` says so, shows when the counts start again and does not run; every other tool keeps working. `katman_account` shows what you have used this month.
24
+
25
+ Choose a plan at https://katman.pro/pricing, then sign in at https://katman.pro/app → **MCP → Create key** and add the key to your MCP config (below). Without a key, or without an active plan, a tool tells you what it would do for your project and how to get the plan, instead of running. `katman_account` always works and shows your plan, this month's audits and sites, and how to upgrade.
26
+
27
+ Accounts that bought an older plan keep what they bought: an Audit Week (no longer sold) until it ends, with the audit and recommendation tools, and an earlier Katman Pro subscription as it was, without these monthly limits.
22
28
 
23
29
  The Katman book and workbook PDFs are sold separately at https://katman.pro/guide; the MCP plans don't include them.
24
30
 
@@ -34,7 +40,7 @@ AI assistants recommend a site only when three gates are open:
34
40
  | **Quote** | The first 1–3 sentences answer the question on their own, with the same one sentence everywhere | Pages that open with "Welcome to…", llms.txt contradicting the pricing page |
35
41
  | **Fit** | There's a page for the situation the customer describes to the assistant | Pages named after the product instead of the customer's problem |
36
42
 
37
- Our own sample site, an AI-powered consumer web app we launched on 21 September 2026, went through these gates in its first week. It got **333 real ChatGPT visitors in 7 days**, and **11 of its 17 paying customers** came from ChatGPT. Source: the site's own analytics, human sessions only. The site is our own product, not a client. The full story is at https://katman.pro/case-studies/sample-site-first-7-days.
43
+ Our own sample site, an AI-powered consumer web app we launched on 21 September 2026, went through these gates in its first week. It got **333 real ChatGPT visitors in 7 days**, and **11 of its 17 paying customers** came from ChatGPT (the site's own analytics, human sessions only, 21–27 September 2026). By 1 October 2026, **503 distinct visitors** had come from ChatGPT, about 416 after removing bots, with 40–90 a day since 23 September. The site is our own product, not a client. The full story is at https://katman.pro/case-studies/sample-site-first-7-days.
38
44
 
39
45
  ## Install
40
46
 
@@ -85,53 +91,53 @@ Open your project in your editor and tell your AI:
85
91
 
86
92
  > Use Katman to make this site recommendable by ChatGPT. Start with katman_start.
87
93
 
88
- Katman then walks your agent through four steps. The Audit Week covers steps 1 and 2, the checks of step 4 and the measurements; Katman Pro adds step 3 (building), IndexNow, monitoring and the dashboard sync:
94
+ Katman then walks your agent through four steps:
89
95
 
90
96
  1. **Research** (`katman_research`): your one sentence (what the customer brings, what you do, what they get, which fear is unnecessary), a not-X note, 10–12 customer situations written without your brand name, competitors (with source and date), proof and a question set.
91
97
  2. **Plan** (`katman_scan_code`, `katman_audit_site`, `katman_plan`): a report of what's broken, with no code changes. Then a page plan and a task list ordered like a 30-day plan.
92
- 3. **Build** (Pro: `katman_prompt` P0–P14, `katman_page_brief`, `katman_generate`): exact prompts for your AI, each with an acceptance criterion, plus generated robots.txt, llms.txt, JSON-LD and analytics snippets.
93
- 4. **Verify** (`katman_audit_site`, `katman_lint_content`; Pro: `katman_indexnow`): Katman checks that each acceptance criterion is really met, then notifies search engines about new or changed URLs only.
98
+ 3. **Build** (`katman_prompt` P0–P14, `katman_page_brief`, `katman_generate`): exact prompts for your AI, each with an acceptance criterion, plus generated robots.txt, llms.txt, JSON-LD and analytics snippets.
99
+ 4. **Verify** (`katman_audit_site`, `katman_lint_content`, `katman_indexnow`): Katman checks that each acceptance criterion is really met, then notifies search engines about new or changed URLs only.
94
100
 
95
101
  Measure every 14 days with `katman_visibility_run` (API) or `katman_manual_check` (answers you paste from the ChatGPT/Claude/Perplexity apps).
96
102
 
97
103
  ## Tools
98
104
 
99
- "Audit Week" in the Plan column means the Audit Week or Katman Pro; "Pro" means Katman Pro only.
105
+ Every tool is in both Katman Pro plans. Only `katman_audit_site` is counted: each run uses one audit of the month.
100
106
 
101
107
  | Tool | Plan | What it does | Writes |
102
108
  |---|---|---|---|
103
- | `katman_account` | Always | Your plan (none, Audit Week with the days left, or Pro), the tools it unlocks, masked account email, last key check, how to get a key or upgrade. Works without a key | — |
104
- | `katman_start` | Audit Week | Shows where you are in the method and the exact next call, with a one-line plan status. Without a plan: what Katman does, the two plans and how to set the key | `config.json` (site URL) |
105
- | `katman_research` | Audit Week | Research template, merge and validation, 12+2 question set | `research.json`, `research.md` |
106
- | `katman_keywords` | Audit Week | Google autocomplete expansion into situation and single-question page ideas (≤40 throttled requests) | `research.json` (optional) |
107
- | `katman_scan_code` | Audit Week | Framework, rendering (SSR/SSG/CSR), routes, head tags, robots/sitemap/llms.txt, JSON-LD, links, images, analytics | `scan.json` |
108
- | `katman_audit_site` | Audit Week | Live audit as AI crawlers see it: bot access through your CDN, JS-off text, 404s, canonicals, sitemap, hreflang, JSON-LD, internal links | `audits/`, `audit.md` |
109
- | `katman_plan` | Audit Week | Page plan and task list with P-codes and acceptance criteria (keeps your checkboxes) | `plan.json`, `plan.md` |
110
- | `katman_lint_content` | Audit Week | Checks a page against the content rules: answer-first, same one sentence, no hype, sourced numbers, FAQ, not-for, links | — |
111
- | `katman_visibility_run` | Audit Week | Asks your question set to ChatGPT, Claude, Gemini, Perplexity and Grok models with live web search via OpenRouter and your own key (API answers are a proxy for the consumer apps). Dry run and cost estimate first; presets economy/full/probe; `samples` for stability | `runs/`, `measurements.md` |
112
- | `katman_manual_check` | Audit Week | Records an answer you paste from a consumer app (logged out, search on); no API cost | `runs/`, `measurements.md` |
113
- | `katman_report` | Audit Week | Shareable report: gates, visibility deltas, competitors, cited domains, next 7 days | `report.md` |
114
- | `katman_prompt` | Pro | Prompt P0–P14 filled with your data, plus Accept / Why / How Katman verifies | — |
115
- | `katman_page_brief` | Pro | Brief for a situation, comparison, pricing, FAQ or about page, and where the file goes in your framework | — |
116
- | `katman_generate` | Pro | robots.txt, llms.txt, Organization/WebSite/SoftwareApplication/FAQ/Breadcrumb JSON-LD, AI-channel analytics snippet, server-side bot counter, IndexNow key | only with `write: true`, never overwrites without `overwrite: true` |
117
- | `katman_indexnow` | Pro | Submits only new or changed URLs to IndexNow, plus a Search Console / Bing / Brave checklist | `indexnow.json` |
118
- | `katman_setup_monitoring` | Pro | GitHub Actions workflow that re-measures weekly with your own keys | `.github/workflows/katman.yml` only with `write: true` |
119
- | `katman_sync` | Pro | Sends your progress to your katman.pro dashboard: gate pass counts, visibility rates by assistant and language, plan progress, top 10 issue titles, dates, Katman version. Never code, file contents, answers, questions or prompts. Dry run (the exact JSON) by default | — |
120
-
121
- Slash-command prompts: `katman-start`, `katman-audit`, `katman-situation-page`, `katman-measure` (each tool they call checks the plan). Resources: the method, the checklists and the content rules (Audit Week), this project's `research.md` (Audit Week) and the raw prompts `katman://prompts/P0`…`P14` (Pro).
109
+ | `katman_account` | Always | Your plan (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, masked account email, last key check, how to get a key or upgrade. Works without a key | — |
110
+ | `katman_start` | Katman Pro | Shows where you are in the method and the exact next call, with a one-line plan status. Without a plan: what Katman does, the two plans and how to set the key | `config.json` (site URL) |
111
+ | `katman_research` | Katman Pro | Research template, merge and validation, 12+2 question set | `research.json`, `research.md` |
112
+ | `katman_keywords` | Katman Pro | Google autocomplete expansion into situation and single-question page ideas (≤40 throttled requests) | `research.json` (optional) |
113
+ | `katman_scan_code` | Katman Pro | Framework, rendering (SSR/SSG/CSR), routes, head tags, robots/sitemap/llms.txt, JSON-LD, links, images, analytics | `scan.json` |
114
+ | `katman_audit_site` | Katman Pro | Live audit as AI crawlers see it: bot access through your CDN, JS-off text, 404s, canonicals, sitemap, hreflang, JSON-LD, internal links. Counted: one run = one audit of the month. Re-read the last result in `audit.md` for free | `audits/`, `audit.md` |
115
+ | `katman_plan` | Katman Pro | Page plan and task list with P-codes and acceptance criteria (keeps your checkboxes) | `plan.json`, `plan.md` |
116
+ | `katman_lint_content` | Katman Pro | Checks a page against the content rules: answer-first, same one sentence, no hype, sourced numbers, FAQ, not-for, links | — |
117
+ | `katman_visibility_run` | Katman Pro | Asks your question set to ChatGPT, Claude, Gemini, Perplexity and Grok models with live web search via OpenRouter and your own key (API answers are a proxy for the consumer apps). Dry run and cost estimate first; presets economy/full/probe; `samples` for stability | `runs/`, `measurements.md` |
118
+ | `katman_manual_check` | Katman Pro | Records an answer you paste from a consumer app (logged out, search on); no API cost | `runs/`, `measurements.md` |
119
+ | `katman_report` | Katman Pro | Shareable report: gates, visibility deltas, competitors, cited domains, next 7 days | `report.md` |
120
+ | `katman_prompt` | Katman Pro | Prompt P0–P14 filled with your data, plus Accept / Why / How Katman verifies | — |
121
+ | `katman_page_brief` | Katman Pro | Brief for a situation, comparison, pricing, FAQ or about page, and where the file goes in your framework | — |
122
+ | `katman_generate` | Katman Pro | robots.txt, llms.txt, Organization/WebSite/SoftwareApplication/FAQ/Breadcrumb JSON-LD, AI-channel analytics snippet, server-side bot counter, IndexNow key | only with `write: true`, never overwrites without `overwrite: true` |
123
+ | `katman_indexnow` | Katman Pro | Submits only new or changed URLs to IndexNow, plus a Search Console / Bing / Brave checklist | `indexnow.json` |
124
+ | `katman_setup_monitoring` | Katman Pro | GitHub Actions workflow that re-measures weekly with your own keys | `.github/workflows/katman.yml` only with `write: true` |
125
+ | `katman_sync` | Katman Pro | Sends your progress to your katman.pro dashboard: gate pass counts, visibility rates by assistant and language, plan progress, top 10 issue titles, dates, Katman version. Never code, file contents, answers, questions or prompts. Dry run (the exact JSON) by default | — |
126
+
127
+ Slash-command prompts: `katman-start`, `katman-audit`, `katman-situation-page`, `katman-measure` (each tool they call checks the plan). Resources: the method, the checklists and the content rules, this project's `research.md` and the raw prompts `katman://prompts/P0`…`P14`.
122
128
 
123
129
  ## What Katman can and can't do
124
130
 
125
131
  - **Reads** files in your project, but skips `node_modules`, `.git` and build output, and **never** reads `.env*`, `.dev.vars*`, keys, certificates or credential files.
126
132
  - **Writes** only to `.katman/` in your project, except `katman_generate` and `katman_setup_monitoring` when you pass `write: true`.
127
- - **Network**: your own site (audits), `openrouter.ai` (visibility runs, with your key, after a dry run), `api.indexnow.org` (only when you submit), `suggestqueries.google.com` (only `katman_keywords`), and `katman.pro` only for two things: checking your `KATMAN_KEY` (at most once a day, and again when a plan ends; only when the key is set) and `katman_sync` (only with `dry_run: false`). **No telemetry**: without a key, nothing is sent to Katman; the key check sends only the key and the Katman version.
128
- - **Your keys** (`KATMAN_KEY`, `OPENROUTER_API_KEY`) are read from the environment and are never printed or written to disk. The licence check is cached in `~/.katman/license.json` under the key's SHA-256 hash (never the key); if katman.pro can't be reached, your plan keeps working for 7 days after the last successful check, never past the end of an Audit Week.
133
+ - **Network**: your own site (audits), `openrouter.ai` (visibility runs, with your key, after a dry run), `api.indexnow.org` (only when you submit), `suggestqueries.google.com` (only `katman_keywords`), and `katman.pro` only for four things: checking your `KATMAN_KEY` (at most once a day, and again when a plan ends; only when the key is set), loading Katman's method (the prompts, page briefs and checklists; once per session, before the first tool your plan includes; it sends the key and the Katman version, and the method stays in memory, never on disk), counting an audit (right before each `katman_audit_site` run or CLI `audit`: it sends the key, the domain being audited and the Katman version, and nothing else) and `katman_sync` (only with `dry_run: false`). **No telemetry**: without a key, nothing is sent to Katman; the key check sends only the key and the Katman version. If katman.pro can't be reached when an audit is about to be counted, the audit runs anyway and its result says the count wasn't checked. The method itself needs katman.pro once per session: if it can't be reached then, the tool says so and you can try again.
134
+ - **Your keys** (`KATMAN_KEY`, `OPENROUTER_API_KEY`) are read from the environment and are never printed or written to disk. The licence check is cached in `~/.katman/license.json` under the key's SHA-256 hash (never the key); if katman.pro can't be reached, your plan keeps working for 7 days after the last successful check, never past the end of an older Audit Week.
129
135
  - **No automation of consumer chat apps.** Katman doesn't drive chatgpt.com, claude.ai or perplexity.ai with a headless browser: their terms forbid programmatic extraction, and it gets accounts banned. It uses the providers' own web-search models through OpenRouter. For the consumer apps, you paste answers into `katman_manual_check`.
130
136
  - **No ranking guarantees.** A single measurement run is a small sample, so re-run the same questions every 14 days.
131
137
 
132
138
  ## Costs
133
139
 
134
- Katman itself: the Audit Week is $3 once for 7 days; Katman Pro is $29/month (founding price) or $290/year. On top of that, API visibility runs use your own OpenRouter credits (the same with either plan); search results are billed as input tokens, so they're not pennies:
140
+ Katman itself: Katman Pro, 1 site is $49/month and Katman Pro, 3 sites is $88/month. On top of that, API visibility runs use your own OpenRouter credits (the same with either plan, and not counted as audits); search results are billed as input tokens, so they're not pennies:
135
141
 
136
142
  | Panel | What | Typical cost per run (14 questions) |
137
143
  |---|---|---|
@@ -144,18 +150,18 @@ The exact estimate is shown before anything runs, and `max_cost_usd` (default $3
144
150
  ## CLI
145
151
 
146
152
  ```bash
147
- npx -y katman-mcp account [--refresh] # always: your plan, days left, how to get a key
148
- npx -y katman-mcp scan [path] # Audit Week or Pro
149
- npx -y katman-mcp audit https://your.site --max 50 # Audit Week or Pro
150
- npx -y katman-mcp keywords "invoice software" --lang en # Audit Week or Pro
151
- npx -y katman-mcp check --root . [--yes] [--max-cost 2] # Audit Week or Pro: visibility run; dry run without --yes
152
- npx -y katman-mcp models # Audit Week or Pro: the default visibility models and prices
153
- npx -y katman-mcp plan --root . # Audit Week or Pro
154
- npx -y katman-mcp report --root . # Audit Week or Pro
155
- npx -y katman-mcp indexnow --root . [--submit] # Pro
153
+ npx -y katman-mcp account [--refresh] # always: your plan, this month's audits and sites, how to get a key
154
+ npx -y katman-mcp scan [path]
155
+ npx -y katman-mcp audit https://your.site --max 50 # counted: one run = one audit of the month
156
+ npx -y katman-mcp keywords "invoice software" --lang en
157
+ npx -y katman-mcp check --root . [--yes] [--max-cost 2] # visibility run; dry run without --yes
158
+ npx -y katman-mcp models # the default visibility models and prices
159
+ npx -y katman-mcp plan --root .
160
+ npx -y katman-mcp report --root .
161
+ npx -y katman-mcp indexnow --root . [--submit]
156
162
  ```
157
163
 
158
- Every command except `account`, `--help` and `--version` reads `KATMAN_KEY` from the environment (`KATMAN_KEY=km_live_... npx -y katman-mcp report --root .`; in GitHub Actions, a repository secret) and, without a plan that covers it, exits with code 4 and a short explanation.
164
+ Every command except `account`, `--help` and `--version` reads `KATMAN_KEY` from the environment (`KATMAN_KEY=km_live_... npx -y katman-mcp report --root .`; in GitHub Actions, a repository secret) and, without an active plan (or when `audit` finds the month's audits or sites used up), exits with code 4 and a short explanation. A scheduled `audit` (a CI job, the workflow from `katman_setup_monitoring`) uses one audit per run: weekly is 4–5 a month.
159
165
 
160
166
  ## Files
161
167
 
package/dist/license.js CHANGED
@@ -1,14 +1,18 @@
1
1
  // Katman licensing (docs/07-APP-SPEC.md §6). Nothing is free: every tool except katman_account needs KATMAN_KEY
2
- // with an active plan, the Audit Week (audit and recommendations, 7 days) or Katman Pro (everything); the plans,
3
- // features and the tool → feature map live in plans.ts.
2
+ // with an active plan: Katman Pro, 1 site or 3 sites (every tool; 5 or 30 audits a month), or an Audit Week bought
3
+ // earlier (audit and recommendations, 7 days); the plans, features and the tool → feature map live in plans.ts.
4
4
  //
5
5
  // - KATMAN_KEY comes from the environment (the MCP config). It is sent only to KATMAN_API_URL (default
6
- // https://katman.pro), in the JSON body of POST /api/web/mcp/verify and /api/web/mcp/sync, and is never
7
- // printed, logged or written anywhere.
6
+ // https://katman.pro), in the JSON body of POST /api/web/mcp/verify, /api/web/mcp/usage and /api/web/mcp/sync,
7
+ // and is never printed, logged or written anywhere.
8
8
  // - Verification runs at most once per `checkAgainAfter` (≤ 24 h), and again as soon as the plan's end date
9
9
  // (planEndsAt) has passed. The answer is cached in ~/.katman/license.json under the key's SHA-256 hash (never the key).
10
10
  // - Offline grace: when katman.pro can't be reached, the last good answer keeps working for 7 days, never past the
11
11
  // end of an Audit Week.
12
+ // - Audits are counted by katman.pro, not here: the audit runs on this machine, so katman_audit_site and the CLI
13
+ // `audit` ask first (useAudit → POST /api/web/mcp/usage, once the plan check has passed). A refusal (audit or site
14
+ // limit) explains and runs nothing; when katman.pro can't be reached or answers 5xx the audit runs anyway (fail
15
+ // open) and the result says the count wasn't checked.
12
16
  // - Every tool goes through withPlan() at registration (katman_start through startLicense()); katman_account is the
13
17
  // only open tool, and the CLI uses the same gate. Without a key nothing is sent to katman.pro.
14
18
  // - Tests inject a LicenseProvider (createServer({ license }) / runCli(args, { license })); there is no env
@@ -21,6 +25,7 @@ import { resolveRoot } from "./context.js";
21
25
  import { HOMEPAGE, VERSION } from "./meta.js";
22
26
  import { AUDIT_FEATURES, PRO_FEATURES, TOOL_FEATURES, toolForFeature } from "./plans.js";
23
27
  import { ok } from "./util.js";
28
+ import { METHOD_INLINE, applyMethod } from "./tools/method/data/_store.js";
24
29
  import { planNotice, projectFacts, renderUpsell, statusLine, upsellStructured } from "./tools/account/upsell.js";
25
30
  export * from "./plans.js";
26
31
  export function allows(status, feature) {
@@ -62,7 +67,7 @@ export function apiBase(env) {
62
67
  return { url: `${u.protocol}//${u.host}${u.pathname.replace(/\/+$/, "")}` };
63
68
  }
64
69
  /** POST JSON. Redirects are not followed (a 3xx must never carry the key to another host). Never throws. */
65
- export const postJson = async (url, body, { timeoutMs }) => {
70
+ export const postJson = async (url, body, { timeoutMs, maxChars = 100_000 }) => {
66
71
  const ctrl = new AbortController();
67
72
  const timer = setTimeout(() => ctrl.abort(), timeoutMs);
68
73
  try {
@@ -77,7 +82,7 @@ export const postJson = async (url, body, { timeoutMs }) => {
77
82
  },
78
83
  body: JSON.stringify(body),
79
84
  });
80
- const text = (await res.text()).slice(0, 100_000);
85
+ const text = (await res.text()).slice(0, maxChars);
81
86
  return { status: res.status, body: text };
82
87
  }
83
88
  catch (e) {
@@ -97,11 +102,17 @@ export const GRACE_MS = 7 * DAY_S * 1000;
97
102
  /** After a failed attempt, wait this long before trying the network again while the grace covers us. */
98
103
  const RETRY_AFTER_FAILURE_MS = 10 * 60 * 1000;
99
104
  const TIMEOUT_MS = 8_000;
105
+ /** The audit check sits in front of every audit: short, and the audit runs anyway when it times out. */
106
+ const USAGE_TIMEOUT_MS = 5_000;
107
+ /** The method data is a few hundred kB, fetched once per process. */
108
+ const METHOD_TIMEOUT_MS = 20_000;
109
+ const METHOD_MAX_CHARS = 8_000_000;
100
110
  /**
101
- * Entries this version writes (verify API v2: plans none / audit / pro). Katman 0.1.x cached free / trial / pro with
102
- * other feature names in the same file; its entries don't count here and are replaced at the next check.
111
+ * Entries this version writes (verify API v2: plans none / audit / pro, now with limits and usage). Katman 0.1.x
112
+ * cached free / trial / pro with other feature names in the same file, and 0.2.x entries have no limits; they don't
113
+ * count here and are replaced at the next check.
103
114
  */
104
- const CACHE_CONTRACT = 2;
115
+ const CACHE_CONTRACT = 3;
105
116
  export function defaultCacheFile() {
106
117
  return path.join(os.homedir(), ".katman", "license.json");
107
118
  }
@@ -152,6 +163,38 @@ function toIso(x) {
152
163
  return new Date(x < 1e12 ? x * 1000 : x).toISOString();
153
164
  return undefined;
154
165
  }
166
+ const isObj = (x) => !!x && typeof x === "object" && !Array.isArray(x);
167
+ const wholeNumber = (x, min, max = 1_000_000) => typeof x === "number" && Number.isInteger(x) && x >= min && x <= max ? x : undefined;
168
+ /** {sites, auditsPerMonth} from katman.pro; anything that isn't two positive whole numbers is no limit. */
169
+ export function parseLimits(x) {
170
+ if (!isObj(x))
171
+ return undefined;
172
+ const sites = wholeNumber(x.sites, 1);
173
+ const auditsPerMonth = wholeNumber(x.auditsPerMonth, 1);
174
+ return sites !== undefined && auditsPerMonth !== undefined ? { sites, auditsPerMonth } : undefined;
175
+ }
176
+ /** {month, audits, sites, resetsAt} from katman.pro; malformed counts are dropped. */
177
+ export function parseUsage(x) {
178
+ if (!isObj(x))
179
+ return undefined;
180
+ const audits = wholeNumber(x.audits, 0);
181
+ const sites = wholeNumber(x.sites, 0);
182
+ const resetsAt = toIso(x.resetsAt);
183
+ const month = typeof x.month === "string" && /^\d{4}-\d{2}$/.test(x.month) ? x.month : undefined;
184
+ return audits !== undefined && sites !== undefined && resetsAt && month ? { month, audits, sites, resetsAt } : undefined;
185
+ }
186
+ /** "0.3.0" >= "0.3.0"; anything that isn't a plain x.y.z version counts as older. */
187
+ export function versionAtLeast(version, min) {
188
+ const parse = (v) => (/^\d+\.\d+\.\d+/.test(v) ? v.split("-")[0].split(".").map(Number) : undefined);
189
+ const a = parse(version);
190
+ const b = parse(min);
191
+ if (!a || !b)
192
+ return false;
193
+ for (let i = 0; i < 3; i++)
194
+ if ((a[i] ?? 0) !== (b[i] ?? 0))
195
+ return (a[i] ?? 0) > (b[i] ?? 0);
196
+ return true;
197
+ }
155
198
  /**
156
199
  * Parse a /api/web/mcp/verify response (spec §6, API v2). Only a 401 (or an explicit ok:false) is a rejection.
157
200
  * Tolerant of older servers: an unknown plan (trial, free…) is "none", `features` is read from the array whatever the
@@ -179,6 +222,10 @@ export function parseVerify(res) {
179
222
  const account = j.account && typeof j.account === "object" ? j.account : {};
180
223
  const emailMasked = typeof account.emailMasked === "string" ? account.emailMasked.slice(0, 120) : undefined;
181
224
  const planEndsAt = toIso(j.planEndsAt) ?? toIso(j.trialEndsAt);
225
+ // Only a plan with limits (Katman Pro, 1 site / 3 sites) carries limits, usage, enforced and updateMcpTo.
226
+ const limits = parseLimits(j.limits);
227
+ const usage = limits ? parseUsage(j.usage) : undefined;
228
+ const updateMcpTo = limits && typeof j.updateMcpTo === "string" && /^\d{1,4}\.\d{1,5}\.\d{1,6}$/.test(j.updateMcpTo) ? j.updateMcpTo : undefined;
182
229
  return {
183
230
  kind: "good",
184
231
  good: {
@@ -187,6 +234,10 @@ export function parseVerify(res) {
187
234
  checkAgainAfter: ttlSeconds(j.checkAgainAfter),
188
235
  ...(planEndsAt ? { planEndsAt } : {}),
189
236
  ...(j.auditWeekUsed === true ? { auditWeekUsed: true } : {}),
237
+ ...(limits ? { limits } : {}),
238
+ ...(usage ? { usage } : {}),
239
+ ...(limits && typeof j.enforced === "boolean" ? { enforced: j.enforced } : {}),
240
+ ...(updateMcpTo ? { updateMcpTo } : {}),
190
241
  ...(emailMasked ? { emailMasked } : {}),
191
242
  },
192
243
  };
@@ -247,6 +298,11 @@ export function createLicenseProvider(deps = {}) {
247
298
  ...(endsAt ? { planEndsAt: endsAt } : {}),
248
299
  ...(ended ? { planEnded: true, ...(ended.plan ? { endedPlan: ended.plan } : {}) } : {}),
249
300
  ...(g.auditWeekUsed || (expired && g.plan === "audit") ? { auditWeekUsed: true } : {}),
301
+ ...(!expired && g.limits ? { limits: g.limits } : {}),
302
+ // The counts start again on the 1st: a count from last month is not shown.
303
+ ...(!expired && g.limits && g.usage && Date.parse(g.usage.resetsAt) > at ? { usage: g.usage } : {}),
304
+ ...(!expired && g.limits && g.enforced !== undefined ? { enforced: g.enforced } : {}),
305
+ ...(!expired && g.updateMcpTo && !versionAtLeast(version, g.updateMcpTo) ? { updateMcpTo: g.updateMcpTo } : {}),
250
306
  ...(g.emailMasked ? { emailMasked: g.emailMasked } : {}),
251
307
  checkedAt: g.checkedAt,
252
308
  nextCheckAt: nextCheck(g),
@@ -266,6 +322,9 @@ export function createLicenseProvider(deps = {}) {
266
322
  return false; // unparsable or in the future (clock change): check again
267
323
  if (!e.result.ok)
268
324
  return age < REJECTED_TTL_S * 1000;
325
+ // "Update katman-mcp" was the answer for an older version: after the update, ask again at once.
326
+ if (e.result.updateMcpTo && versionAtLeast(version, e.result.updateMcpTo))
327
+ return false;
269
328
  // Once the plan's end date has passed, check again at once: an Audit Week ends, Pro renews into a new period.
270
329
  const ends = endsAfterCheck(e.result, checked);
271
330
  if (ends !== undefined && ends <= at)
@@ -333,6 +392,18 @@ export function createLicenseProvider(deps = {}) {
333
392
  await writeCache(cacheFile, cache);
334
393
  return status;
335
394
  }
395
+ /** Keep the newest counts in the cached answer, so katman_account shows this month's audits without another check. */
396
+ async function remember(key, limits, usage) {
397
+ const server = apiBase(env()).url ?? HOMEPAGE;
398
+ const cache = await readCache(cacheFile);
399
+ const e = cache.entries[hashKey(key)];
400
+ if (!e || e.contract !== CACHE_CONTRACT || (e.base ?? HOMEPAGE) !== server || !e.result?.ok)
401
+ return;
402
+ e.result = { ...e.result, limits, usage };
403
+ if (e.good)
404
+ e.good = { ...e.good, limits, usage };
405
+ await writeCache(cacheFile, cache);
406
+ }
336
407
  return {
337
408
  hasKey: () => !!readKey(env()).key,
338
409
  baseUrl: () => apiBase(env()).url ?? HOMEPAGE,
@@ -360,6 +431,74 @@ export function createLicenseProvider(deps = {}) {
360
431
  inflight.set(flightKey, p);
361
432
  return p;
362
433
  },
434
+ async loadMethod() {
435
+ const { key } = readKey(env());
436
+ if (!key || !KEY_RE.test(key))
437
+ return { ok: false, error: "KATMAN_KEY is not set" };
438
+ const base = apiBase(env());
439
+ if (!base.url)
440
+ return { ok: false, error: base.error ?? "no API URL" };
441
+ const res = await post(`${base.url}/api/web/mcp/method`, { key, mcpVersion: version }, { timeoutMs: METHOD_TIMEOUT_MS, maxChars: METHOD_MAX_CHARS });
442
+ let j;
443
+ try {
444
+ const x = JSON.parse(res.body);
445
+ if (isObj(x))
446
+ j = x;
447
+ }
448
+ catch {
449
+ j = undefined;
450
+ }
451
+ if (res.status === 200 && j?.ok === true && isObj(j.data))
452
+ return { ok: true, data: j.data };
453
+ if (res.status === 401)
454
+ return { ok: false, error: "katman.pro did not accept this KATMAN_KEY" };
455
+ if (res.status === 403)
456
+ return { ok: false, error: "this key has no active Katman plan" };
457
+ const code = typeof j?.error === "string" && /^[a-z_]{1,40}$/.test(j.error) ? ` ${j.error}` : "";
458
+ const why = res.status === 0 ? res.error || "network error" : `HTTP ${res.status}${code}`;
459
+ return { ok: false, error: scrub(why, key).slice(0, 120) };
460
+ },
461
+ async useAudit(domain) {
462
+ const { key } = readKey(env());
463
+ if (!key || !KEY_RE.test(key))
464
+ return { allowed: false, reason: "no_plan" };
465
+ const unchecked = (error) => ({ allowed: true, unchecked: true, error: scrub(error, key).slice(0, 120) });
466
+ const base = apiBase(env());
467
+ if (!base.url)
468
+ return unchecked(base.error ?? "no API URL");
469
+ const res = await post(`${base.url}/api/web/mcp/usage`, { key, kind: "audit", domain, mcpVersion: version }, { timeoutMs: USAGE_TIMEOUT_MS });
470
+ let j;
471
+ try {
472
+ const x = JSON.parse(res.body);
473
+ if (isObj(x))
474
+ j = x;
475
+ }
476
+ catch {
477
+ j = undefined;
478
+ }
479
+ if (res.status === 401)
480
+ return { allowed: false, reason: j?.error === "revoked" ? "revoked" : "invalid_key" };
481
+ if (res.status === 200 && j?.ok === true && typeof j.allowed === "boolean") {
482
+ const limits = parseLimits(j.limits);
483
+ const usage = parseUsage(j.usage);
484
+ const reason = j.reason === "audit_limit" || j.reason === "site_limit" || j.reason === "no_plan" ? j.reason : undefined;
485
+ const plan = j.plan === "audit" || j.plan === "pro" || j.plan === "none" ? j.plan : undefined;
486
+ if (limits && usage)
487
+ await remember(key, limits, usage).catch(() => undefined);
488
+ return {
489
+ allowed: j.allowed,
490
+ ...(j.allowed ? {} : { reason: reason ?? "no_plan" }),
491
+ ...(plan ? { plan } : {}),
492
+ ...(typeof j.enforced === "boolean" ? { enforced: j.enforced } : {}),
493
+ limits: limits ?? null,
494
+ ...(usage ? { usage } : {}),
495
+ };
496
+ }
497
+ // Anything else (no network, a timeout, 5xx, a rate limit, a host without this endpoint, an answer this version
498
+ // doesn't understand): the audit runs, and the result says the count wasn't checked.
499
+ const code = typeof j?.error === "string" && /^[a-z_]{1,40}$/.test(j.error) ? j.error : undefined;
500
+ return unchecked(res.status === 0 ? res.error || "network error" : res.status >= 300 && res.status < 400 ? `unexpected redirect (HTTP ${res.status}); check KATMAN_API_URL` : `HTTP ${res.status}${code ? ` ${code}` : ""}`);
501
+ },
363
502
  async sync(body) {
364
503
  const { key } = readKey(env());
365
504
  if (!key || !KEY_RE.test(key))
@@ -415,7 +554,8 @@ export function licenseOf(ctx) {
415
554
  }
416
555
  // ---------------------------------------------------------------------------------------------------------------
417
556
  // Gate
418
- async function statusOf(provider) {
557
+ /** The provider's status; a provider that throws counts as unreachable. */
558
+ export async function statusOf(provider) {
419
559
  try {
420
560
  return await provider.status();
421
561
  }
@@ -425,7 +565,41 @@ async function statusOf(provider) {
425
565
  }
426
566
  export async function requireFeature(feature, provider) {
427
567
  const status = await statusOf(provider);
428
- return { allowed: allows(status, feature), status };
568
+ const allowed = allows(status, feature);
569
+ if (allowed)
570
+ await ensureMethod(provider);
571
+ return { allowed, status };
572
+ }
573
+ // ---------------------------------------------------------------------------------------------------------------
574
+ // Method data (the published package carries none; see src/tools/method/data/_store.ts)
575
+ let methodLoaded = METHOD_INLINE;
576
+ let methodLoading;
577
+ /** Thrown when a tool needs the method and katman.pro couldn't send it; the message is shown to the user. */
578
+ export class MethodUnavailableError extends Error {
579
+ }
580
+ /**
581
+ * Make sure the method data is in memory before a paid tool runs: fetched from katman.pro once per process with the
582
+ * key, never written to disk. Throws MethodUnavailableError with a readable reason when it can't be loaded.
583
+ */
584
+ export async function ensureMethod(provider) {
585
+ if (methodLoaded)
586
+ return;
587
+ if (!provider.loadMethod)
588
+ throw new MethodUnavailableError("Katman could not load its method data (this license provider can't fetch it).");
589
+ methodLoading ??= provider.loadMethod().then((r) => {
590
+ if (r.ok) {
591
+ applyMethod(r.data);
592
+ methodLoaded = true;
593
+ }
594
+ else {
595
+ methodLoading = undefined;
596
+ }
597
+ return r;
598
+ });
599
+ const r = await methodLoading;
600
+ if (!r.ok) {
601
+ throw new MethodUnavailableError(`Katman needs to reach ${provider.baseUrl()} once per session to load its method for your plan, and couldn't (${r.error}). Check the connection and KATMAN_KEY, then try again.`);
602
+ }
429
603
  }
430
604
  /**
431
605
  * Wrap a tool's handler at registration. Without a plan that includes the feature it returns a short explanation
@@ -449,12 +623,24 @@ export function withPlan(ctx, feature, handler) {
449
623
  return ok(renderUpsell({ tool, feature, status: gate.status, facts, args: a, surface: "mcp" }), upsellStructured(tool, feature, gate.status));
450
624
  };
451
625
  }
626
+ /** Ask whether an audit of this domain may run (and have it counted). Never throws: a failing provider counts as unchecked, the audit runs. */
627
+ export async function meterAudit(provider, domain) {
628
+ try {
629
+ return await provider.useAudit(domain);
630
+ }
631
+ catch (e) {
632
+ return { allowed: true, unchecked: true, error: e instanceof Error ? e.message.slice(0, 120) : "error" };
633
+ }
634
+ }
452
635
  /** For katman_start: the gate, the plan line, and a notice when the recommended next tool isn't in the plan. */
453
636
  export async function startLicense(ctx) {
454
637
  const provider = licenseOf(ctx);
455
638
  const status = await statusOf(provider);
639
+ const allowed = allows(status, "start");
640
+ if (allowed)
641
+ await ensureMethod(provider);
456
642
  return {
457
- allowed: allows(status, "start"),
643
+ allowed,
458
644
  status,
459
645
  line: statusLine(status),
460
646
  notice: (tool) => {
@@ -467,6 +653,9 @@ export async function startLicense(ctx) {
467
653
  plan: status.plan,
468
654
  ...(status.planEndsAt ? { planEndsAt: status.planEndsAt } : {}),
469
655
  ...(status.planEnded ? { planEnded: true } : {}),
656
+ ...(status.limits ? { limits: status.limits } : {}),
657
+ ...(status.usage ? { usage: status.usage } : {}),
658
+ ...(status.updateMcpTo ? { updateMcpTo: status.updateMcpTo } : {}),
470
659
  },
471
660
  };
472
661
  }
package/dist/meta.js CHANGED
@@ -1,19 +1,31 @@
1
- export const VERSION = "0.2.0";
1
+ export const VERSION = "0.3.0";
2
2
  export const HOMEPAGE = "https://katman.pro";
3
- /** Plans (docs/07-APP-SPEC.md §1): nothing is free. Audit Week = $3 once for 7 days; Katman Pro = the founding price. */
3
+ /**
4
+ * Plans (docs/07-APP-SPEC.md §1): nothing is free. Katman Pro, 1 site = $49/month (1 site, 5 audits a month); Katman Pro,
5
+ * 3 sites = $88/month (3 sites, 30 audits a month); both include every tool. The Audit Week ($3 once, 7 days) is no
6
+ * longer sold; the package still recognises it for accounts that bought it.
7
+ */
4
8
  export const APP_URL = `${HOMEPAGE}/app`;
5
9
  export const KEYS_URL = `${APP_URL}/mcp`;
6
10
  export const BILLING_URL = `${APP_URL}/billing`;
11
+ /** The billing page opened on one plan (product ids pro_1 / pro_3); signed-out people are asked to log in first. */
12
+ export const BUY_PRO_1_URL = `${BILLING_URL}?buy=pro_1`;
13
+ export const BUY_PRO_3_URL = `${BILLING_URL}?buy=pro_3`;
7
14
  export const PRICING_URL = `${HOMEPAGE}/pricing`;
8
15
  /** How to add KATMAN_KEY to each editor's MCP config. */
9
16
  export const KEY_DOCS_URL = `${HOMEPAGE}/docs/getting-started`;
10
- export const AUDIT_WEEK_DAYS = 7;
11
- export const AUDIT_WEEK_PRICE = "$3";
12
- export const PRO_PRICE = "$29/month";
13
- /** Audit Week in one phrase. */
14
- export const AUDIT_PRICE_NOTE = "$3 once for 7 days, no auto-renewal, one per account";
15
- /** Katman Pro in one phrase. */
16
- export const PRO_PRICE_NOTE = "$29/month founding price for the first 100 customers, locked while you stay subscribed (later $49), or $290/year";
17
+ export const PRO_1_PRICE = "$49/month";
18
+ export const PRO_3_PRICE = "$88/month";
19
+ /** Katman Pro, 1 site, in one phrase. */
20
+ export const PRO_1_NOTE = "Katman Pro, 1 site $49/month (1 site, 5 audits a month)";
21
+ /** Katman Pro, 3 sites, in one phrase. */
22
+ export const PRO_3_NOTE = "Katman Pro, 3 sites $88/month (3 sites, 30 audits a month)";
23
+ /** Both plans in one phrase. */
24
+ export const PRO_PRICE_NOTE = `${PRO_1_NOTE} or ${PRO_3_NOTE}; both include every tool`;
25
+ export const PRO_PRICE_NOTE_TR = "Katman Pro, 1 site ayda 49 $ (1 site, ayda 5 denetim) ya da Katman Pro, 3 site ayda 88 $ (3 site, ayda 30 denetim); ikisinde de tüm araçlar var";
26
+ /** How audits are counted: the same sentence as katman.pro/pricing (en and tr). */
27
+ export const AUDIT_COUNT_NOTE = "An audit is one run of katman_audit_site on a live site. Sites and audits are counted per calendar month (UTC): a site is a domain you audited that month. Re-reading earlier results, the other tools and visibility runs are not counted.";
28
+ export const AUDIT_COUNT_NOTE_TR = "Denetim, canlı bir sitede katman_audit_site’ın bir çalıştırmasıdır. Siteler ve denetimler her takvim ayı (UTC) için sayılır: site, o ay denetlediğiniz bir alan adıdır. Önceki sonuçları yeniden okumak, diğer araçlar ve görünürlük ölçümleri sayılmaz.";
17
29
  /** Sent to the client model at initialization. Keep it short and imperative. */
18
30
  export const SERVER_INSTRUCTIONS = `Katman makes this project's public website readable, quotable and recommendable by AI assistants (ChatGPT, Claude, Gemini, Perplexity, Copilot) and findable in Google, Bing and Brave.
19
31
 
@@ -22,10 +34,10 @@ It follows the Katman method: 4 steps (1 Research → 2 Plan → 3 Build → 4 V
22
34
  - Quote: the first 1–3 sentences answer the question on their own, with the brand's one-sentence description.
23
35
  - Fit: there is a page written for the situation the customer describes to the assistant.
24
36
 
25
- Plans: every tool except katman_account needs KATMAN_KEY with an active plan in the MCP config.
26
- - Audit Week ($3 for 7 days): katman_start, katman_research, katman_scan_code, katman_audit_site, katman_keywords, katman_plan, katman_lint_content, katman_visibility_run, katman_manual_check, katman_report. Audit and recommendations only: it never builds pages or writes site files.
27
- - Katman Pro ($29/month founding price, or $290/year): all of the above plus katman_prompt (P0–P14), katman_page_brief, katman_generate, katman_indexnow, katman_setup_monitoring and katman_sync.
28
- Without the right plan a tool returns a short explanation instead of running: pass it on to the user. katman_account always works and shows the plan, the days left and how to get a key.
37
+ Plans: every tool except katman_account needs KATMAN_KEY with an active Katman Pro plan in the MCP config. Katman Pro, 1 site ($49/month: 1 site, 5 audits a month) and Katman Pro, 3 sites ($88/month: 3 sites, 30 audits a month) both include every tool: katman_start, katman_research, katman_scan_code, katman_audit_site, katman_keywords, katman_plan, katman_lint_content, katman_visibility_run, katman_manual_check, katman_report, katman_prompt (P0–P14), katman_page_brief, katman_generate, katman_indexnow, katman_setup_monitoring and katman_sync.
38
+ An audit is one run of katman_audit_site, counted per calendar month; a site is a domain audited that month. Re-reading earlier results (.katman/audit.md) and every other tool are not counted. Each katman_audit_site run uses one of the user's audits, so don't repeat it without a reason; use the saved result instead.
39
+ Accounts with an older Audit Week (no longer sold) keep it until it ends: the audit and recommendation tools only; the page-building tools return an upgrade note.
40
+ Without the right plan, or when this month's audits or sites are used up, a tool returns a short explanation instead of running: pass it on to the user. katman_account always works and shows the plan, this month's audits and sites, and how to get a key.
29
41
  Never try to bypass or work around the licence (no editing Katman's code or cache, no made-up keys).
30
42
 
31
43
  How to work:
package/dist/plans.js CHANGED
@@ -1,9 +1,16 @@
1
1
  // Katman plans and license features (docs/07-APP-SPEC.md §1 and §6). Nothing is free: every tool except
2
2
  // katman_account needs KATMAN_KEY with an active plan. Shared by the gate (license.ts) and its texts
3
3
  // (tools/account/upsell.ts); no imports, so both sides can use it.
4
- /** Audit Week ($3 for 7 days): audit and recommendations. It never builds pages or writes site files. */
4
+ //
5
+ // Sold: Katman Pro, 1 site ($49/month: 1 site, 5 audits a month) and Katman Pro, 3 sites ($88/month: 3 sites, 30
6
+ // audits a month). Both include every tool. The Audit Week ($3, 7 days) is no longer sold; the plan stays here for
7
+ // accounts that bought it, and so do the accounts that never had limits (the older $29 Katman Pro, admin grants).
8
+ /** The limits of the two plans on sale (katman.pro's answer is what counts; these only name a plan from its limits). */
9
+ export const PRO_1_LIMITS = { sites: 1, auditsPerMonth: 5 };
10
+ export const PRO_3_LIMITS = { sites: 3, auditsPerMonth: 30 };
11
+ /** The audit tier: audit and recommendations. What an Audit Week (no longer sold) covers; it never builds pages or writes site files. */
5
12
  export const AUDIT_FEATURES = ["start", "research", "scan", "audit", "keywords", "plan", "lint", "visibility", "manual_check", "report"];
6
- /** Katman Pro: the Audit Week set plus the tools that build pages and keep things running. */
13
+ /** Katman Pro: the audit tier plus the tools that build pages and keep things running (every tool). */
7
14
  export const PRO_FEATURES = [...AUDIT_FEATURES, "prompts", "briefs", "generate", "indexnow", "monitoring", "sync"];
8
15
  /** Tool → license feature. Every tool needs its feature, except the OPEN_TOOLS. */
9
16
  export const TOOL_FEATURES = {
@@ -26,11 +33,11 @@ export const TOOL_FEATURES = {
26
33
  };
27
34
  /** Tools that always work, even without a key. */
28
35
  export const OPEN_TOOLS = ["katman_account"];
29
- /** Appended to the description of every Audit Week tool. */
30
- export const AUDIT_NOTE = " (Audit Week or Pro — needs KATMAN_KEY)";
36
+ /** Appended to the description of every audit-tier tool. */
37
+ export const AUDIT_NOTE = " (Katman Pro — needs KATMAN_KEY)";
31
38
  /** Appended to the description of every Pro-only tool. */
32
39
  export const PRO_NOTE = " (Katman Pro — needs KATMAN_KEY)";
33
- /** True for the features only Katman Pro unlocks (not in the Audit Week). */
40
+ /** True for the features only Katman Pro unlocks (not in the older Audit Week). */
34
41
  export function isProOnly(feature) {
35
42
  return !AUDIT_FEATURES.includes(feature);
36
43
  }
@@ -41,7 +48,7 @@ export function tierNote(feature) {
41
48
  export function toolForFeature(feature) {
42
49
  return Object.keys(TOOL_FEATURES).find((t) => TOOL_FEATURES[t] === feature) ?? `katman_${feature}`;
43
50
  }
44
- /** The tools of one tier: "audit" = the Audit Week tools (also in Pro), "pro" = the Pro-only tools. */
51
+ /** The tools of one tier: "audit" = the audit tier (what an Audit Week covers; also in Pro), "pro" = the Pro-only tools. */
45
52
  export function toolsOf(tier) {
46
53
  return Object.keys(TOOL_FEATURES).filter((t) => isProOnly(TOOL_FEATURES[t]) === (tier === "pro"));
47
54
  }