@nuxtseo/cli 0.1.4 → 0.2.1

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.
@@ -0,0 +1,22 @@
1
+ import type { CliResult } from './failures.js';
2
+ export declare const SKILL_NAME = "nuxtseo-cli";
3
+ export declare const SKILL_AGENTS: readonly ['claude', 'codex'];
4
+ export type SkillAgent = typeof SKILL_AGENTS[number];
5
+ export interface SkillInstallation {
6
+ agent: SkillAgent;
7
+ source: string;
8
+ destination: string;
9
+ }
10
+ /**
11
+ * The skill shipped inside this package. A global install puts it outside the
12
+ * project, so the documented `cp -R node_modules/...` path does not exist for
13
+ * the user who most needs it. Resolve it from this module instead.
14
+ */
15
+ export declare function skillSourceDirectory(moduleUrl?: string): string;
16
+ export declare function skillDestination(homeDirectory: string, agent: SkillAgent): string;
17
+ export declare function installSkill(options: {
18
+ agent: SkillAgent;
19
+ homeDirectory: string;
20
+ target?: string;
21
+ sourceDirectory?: string;
22
+ }): Promise<CliResult<SkillInstallation>>;
package/dist/skill.js ADDED
@@ -0,0 +1,56 @@
1
+ import { cp, mkdir, stat } from 'node:fs/promises';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join, relative } from 'pathe';
4
+ import { EXIT_CODE, fail, ok } from './failures.js';
5
+ export const SKILL_NAME = 'nuxtseo-cli';
6
+ /**
7
+ * Directories inside the packaged skill that exist for this repository and not
8
+ * for the person installing it. `npm` keeps them out of the tarball through the
9
+ * `files` negation; this keeps them out of an installed skill directory too,
10
+ * because a negation cannot reach a recursive copy.
11
+ */
12
+ const SKILL_LOCAL_ONLY = new Set(['evals']);
13
+ export const SKILL_AGENTS = ['claude', 'codex'];
14
+ const AGENT_DIRECTORIES = {
15
+ claude: '.claude',
16
+ codex: '.codex',
17
+ };
18
+ /**
19
+ * The skill shipped inside this package. A global install puts it outside the
20
+ * project, so the documented `cp -R node_modules/...` path does not exist for
21
+ * the user who most needs it. Resolve it from this module instead.
22
+ */
23
+ export function skillSourceDirectory(moduleUrl = import.meta.url) {
24
+ return join(dirname(fileURLToPath(moduleUrl)), '..', 'skills', SKILL_NAME);
25
+ }
26
+ export function skillDestination(homeDirectory, agent) {
27
+ return join(homeDirectory, AGENT_DIRECTORIES[agent], 'skills', SKILL_NAME);
28
+ }
29
+ export async function installSkill(options) {
30
+ const source = options.sourceDirectory ?? skillSourceDirectory();
31
+ const readable = await stat(source).catch(() => {
32
+ // A missing directory and an unreadable one need the same repair, and the
33
+ // next line reports the path either way.
34
+ return null;
35
+ });
36
+ if (!readable?.isDirectory()) {
37
+ return fail(EXIT_CODE.infrastructure, `skill_source_missing: The packaged skill is missing at ${source}.\nReinstall @nuxtseo/cli, then run the command again.`);
38
+ }
39
+ const destination = options.target
40
+ ? join(options.target, SKILL_NAME)
41
+ : skillDestination(options.homeDirectory, options.agent);
42
+ const written = await mkdir(dirname(destination), { recursive: true })
43
+ .then(() => cp(source, destination, {
44
+ recursive: true,
45
+ force: true,
46
+ filter: (entry) => {
47
+ const [segment] = relative(source, entry).split('/');
48
+ return !segment || !SKILL_LOCAL_ONLY.has(segment);
49
+ },
50
+ }))
51
+ .then(() => ok(undefined))
52
+ .catch(cause => fail(EXIT_CODE.infrastructure, `skill_install_failed: Could not write the skill to ${destination}.\nCheck the directory permissions, then run the command again.`, cause));
53
+ if (written._tag === 'Err')
54
+ return written;
55
+ return ok({ agent: options.agent, source, destination });
56
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxtseo/cli",
3
3
  "type": "module",
4
- "version": "0.1.4",
4
+ "version": "0.2.1",
5
5
  "description": "Command line interface for the NuxtSEO public API.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://nuxtseo.com/pro",
@@ -25,26 +25,27 @@
25
25
  },
26
26
  "files": [
27
27
  "dist",
28
- "skills"
28
+ "skills",
29
+ "!skills/**/evals"
29
30
  ],
30
31
  "engines": {
31
32
  "node": ">=22"
32
33
  },
33
34
  "dependencies": {
34
- "@clack/prompts": "^1.7.0",
35
+ "@clack/prompts": "^1.8.0",
36
+ "@nuxtseo/protocol": "^0.2.1",
37
+ "@nuxtseo/sdk": "^0.2.1",
35
38
  "citty": "^0.2.2",
36
- "pathe": "^2.0.3",
37
- "@nuxtseo/sdk": "^0.1.4"
39
+ "pathe": "^2.0.3"
38
40
  },
39
41
  "optionalDependencies": {
40
- "@napi-rs/keyring": "^1.3.0"
42
+ "@napi-rs/keyring": "^2.0.0"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@arethetypeswrong/cli": "^0.18.5",
44
- "@types/node": "^26.2.0",
46
+ "@types/node": "^26.5.1",
45
47
  "publint": "^0.3.24",
46
- "typescript": "npm:typescript-native-bridge@6.0.3-bridge.10.tsgo.7.0.2",
47
- "@nuxtseo/protocol": "0.1.4"
48
+ "typescript": "npm:typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2"
48
49
  },
49
50
  "publishConfig": {
50
51
  "access": "public"
@@ -1,75 +1,78 @@
1
1
  ---
2
2
  name: nuxtseo-cli
3
- description: Drive the `nuxtseo` CLI for Site triage, Search Console rows, keyword and competitor research, SERP analysis, rankings, link opportunities, Content Briefs, content decay, duplicate clusters, scans, and issue resolution. Use whenever the user mentions NuxtSEO, the `nuxtseo` command, Site SEO work, or NuxtSEO automation.
3
+ description: Drives the `nuxtseo` CLI to triage a Site through the NuxtSEO public API: Site status and ranked issues, Search Console rows, route-family indexing, field and lab Core Web Vitals, Lighthouse Scans, Page Issues, keyword competitor and domain research, backlinks, SERP analysis, rankings, link opportunities, Content Briefs, content decay, duplicate clusters, chart annotations, and issue resolution. Use whenever the user mentions NuxtSEO, the `nuxtseo` command, Site SEO work, or NuxtSEO automation.
4
4
  ---
5
5
 
6
6
  # NuxtSEO CLI
7
7
 
8
8
  `nuxtseo` reads a Site through the NuxtSEO public API. Every command goes
9
9
  through `@nuxtseo/sdk`. The CLI never calls MCP or private routes. Live
10
- research reaches providers only through a declared public API operation.
10
+ research reaches a provider only through a declared public API operation.
11
11
 
12
- Use it to answer "what is wrong with this site and what did I break", then fix
13
- the code in the repo you are working in.
12
+ Use it to answer "what is wrong with this site, and what did I break". Then fix
13
+ the code in the repository you are working in.
14
14
 
15
- ## Data boundaries
15
+ ## Reference files
16
16
 
17
- Most commands read stored Site evidence. Three commands can start live research:
17
+ Read each one before you act on its subject:
18
18
 
19
- - `research keywords`
20
- - `research serp`
21
- - `research rankings`
22
-
23
- These commands use the Team research allowance when the server misses its
24
- cache. Keyword JSON reports cache use in `evidence`. SERP and ranking JSON
25
- report `cached`.
26
-
27
- `search status` reads stored connection state. It never waits for Google.
28
- `search analytics` reads Search Console rows through the public API.
29
- `search indexing` reads retained URL Inspection coverage through the public API.
19
+ - [references/commands.md](references/commands.md): every command, with its
20
+ flags, ranges, and defaults. Read it instead of guessing a flag.
21
+ - [references/protocol.md](references/protocol.md): response envelopes, paging,
22
+ and exit codes. Read it before you parse output or handle a failure.
23
+ - [references/indexing.md](references/indexing.md): how to answer "is this
24
+ indexed". Read it before you report an indexed count, or name the URLs
25
+ Google indexed.
30
26
 
31
27
  ## Get the binary
32
28
 
33
29
  ```sh
34
- npx -y @nuxtseo/cli@latest --version # no install
30
+ pnpm dlx @nuxtseo/cli@latest --version # no install
35
31
  pnpm add -g @nuxtseo/cli # or npm install --global
36
32
  ```
37
33
 
38
- Node 22 or newer is required. Inside the NuxtSEO monorepo itself, the package is
39
- not published, so run the built entry directly:
34
+ Node 22 or newer is required.
35
+
36
+ Inside the NuxtSEO monorepo the package is not published. Run the built entry
37
+ directly:
40
38
 
41
39
  ```sh
42
40
  node packages/cli/dist/cli-entry.js --version
43
41
  ```
44
42
 
45
- Exit `127` or `command not found` means the CLI is absent. That is not exit `3`;
46
- no token has been checked yet. Install it, then continue.
43
+ Exit `127` or `command not found` means the CLI is absent. That is not exit
44
+ `3`; no token has been checked yet. Install it, then continue.
47
45
 
48
46
  ## Before the first command
49
47
 
50
48
  ```sh
51
- nuxtseo whoami --json # validates the token, lists Site count
49
+ nuxtseo whoami --json # validates the token and reports its scopes
52
50
  ```
53
51
 
52
+ `whoami` returns the Team, the role, the granted scopes, and the token expiry.
53
+ Read `data.scopes` before a write command. A missing scope is exit `4`, and the
54
+ scope list tells you that before you spend the request.
55
+
54
56
  Exit `3` means there is no working token. Ask the user to run `nuxtseo login`
55
- themselves: it opens a browser, they approve the pairing, and the CLI stores the
56
- token. Do not run `login` for them; it needs a person at the browser, and in a
57
- non-interactive shell it falls back to reading a token from stdin instead.
57
+ themselves. It opens a browser, they approve the pairing, and the CLI stores
58
+ the token. Do not run `login` for them. It needs a person at the browser, and
59
+ in a non-interactive shell it falls back to reading a token from stdin.
58
60
 
59
61
  The alternative is a token created at
60
62
  <https://nuxtseo.com/pro/dashboard/settings/api-tokens> and exported as
61
63
  `NUXTSEO_TOKEN`. Either way the token is bound to a Team role, and that role
62
64
  gates what it can do.
63
65
 
64
- Never put a token in a command argument, a file you write, or a commit. `login`
65
- reads a token from a hidden prompt or stdin only.
66
+ Never put a token in a command argument, a file you write, or a commit.
67
+ `login` reads a token from a hidden prompt or stdin only.
66
68
 
67
69
  Resolve the Site once and reuse it. `nuxtseo sites list --json` prints every
68
70
  accessible Site with its ID.
69
71
 
70
72
  ## How to call it as an agent
71
73
 
72
- Always pass `--json` and always pass `--site <site-id>`.
74
+ Always pass `--json`. Pass `--site <site-id>` for Site commands.
75
+ `feedback submit` needs no Site.
73
76
 
74
77
  ```sh
75
78
  nuxtseo actions list --site site_123 --json
@@ -79,96 +82,258 @@ nuxtseo actions list --site site_123 --json
79
82
  also disables prompts. Diagnostics, warnings, spinners, and failure messages go
80
83
  to stderr, so stdout stays parseable.
81
84
 
82
- `--site` matters for a second reason: an explicit Site ID goes straight to the
83
- operation and skips the Sites read, so a token whose role cannot list Sites
84
- still works. If more than one Site is accessible and `--site` is absent, the CLI
85
+ `--site` matters for a second reason. An explicit Site ID goes straight to the
86
+ operation and skips the Sites read. A token whose role cannot list Sites still
87
+ works. If more than one Site is accessible and `--site` is absent, the CLI
85
88
  exits `5` rather than guessing.
86
89
 
87
90
  Other flags worth knowing:
88
91
 
89
92
  | Flag | Use it when |
90
93
  | --- | --- |
91
- | `--yes`, `-y` | The command mutates. Without it, a non-interactive run exits `2`. |
92
- | `--timeout-ms <ms>` | The default 30000 ms deadline is too short. Maximum is 300000. |
93
- | `--api-url <url>` | Testing against a non-production host. |
94
- | `--no-input` | Running in a TTY but no prompt is wanted. `--json` already implies this. |
94
+ | `--yes`, `-y` | The command mutates. Without it, a non-interactive run exits `2` |
95
+ | `--timeout-ms <ms>` | The default 30000 ms deadline is too short. Maximum is 300000 |
96
+ | `--api-url <url>` | Testing against a non-production host |
97
+ | `--no-input` | Running in a TTY but no prompt is wanted. `--json` already implies this |
95
98
 
96
99
  Unknown options exit `2` before any network work, so a typo costs nothing.
97
100
 
98
- Parse JSON. Never scrape human output. Before parsing envelopes, paging, or
99
- handling failures, read [CLI protocol](references/protocol.md).
100
-
101
- ## Commands
102
-
103
- | Command | Reads | Notes |
104
- | --- | --- | --- |
105
- | `whoami` | Token validity, API host, Site count | Cheapest health check |
106
- | `sites list` | Every accessible Site | Source of Site IDs |
107
- | `sites use <site-id>` | – | Persists a default Site for the human, not for you |
108
- | `usage` | Plan and meters | `--group integrations\|compute\|capacity` |
109
- | `actions list` | Server-ranked issues and opportunities | `--limit 1..25` (default 10), `--offset`. JSON carries `evidence.freshness`; `verdict: "aged"` means the observation is over a day old, so live-check before fixing |
110
- | `actions show <action-id>` | One issue or opportunity plus evidence | `--limit 1..100` (default 50), `--group-id`, `--cursor` |
111
- | `actions resolve <action-id>` | – | Mutation. Claims the issue or opportunity and starts server verification |
112
- | `backlinks recoverable` | Stored recoverable backlinks | `--limit 1..200`, `--offset` |
113
- | `mentions list` | Stored mentions | `--limit 1..200` |
114
- | `page inspect <url>` | Stored observations, Lighthouse, keywords | `--limit 1..200`, `--offset`, `--include-resolved`. JSON carries `observations.coverage`: `never-scanned` means no source recorded this Page, `scanned-clear` means recorded and all clear |
115
- | `page scan <url>` | – | Mutation. Starts mobile and desktop scans |
116
- | `performance` | Site performance overview | Medians for perf, a11y, SEO, LCP, TBT, CLS |
117
- | `search status` | Stored Search Console connection | Provider free |
118
- | `search analytics <pages\|keywords>` | Search Console Page or query rows | `--period`, `--limit`, `--page`, `--search` |
119
- | `search indexing <summary\|urls>` | Retained URL Inspection coverage | `summary` returns indexed counts; `urls` supports `--issue`, `--status`, `--limit`, `--offset` |
120
- | `research overview` | Stored Site and competitor research | Metrics, history, gaps, and quick wins |
121
- | `research keywords <topic>` | Live keyword ideas | Volume, difficulty, intent, and cost data |
122
- | `research serp <keyword>` | Live SERP snapshot | Results, features, and fetch time |
123
- | `research rankings <domain>` | Live domain rankings | Current keywords and domain metrics |
124
- | `audit link-opportunities` | Stored internal link opportunities | Includes crawl coverage |
125
- | `audit content-decay` | Stored decaying Pages | Includes Search Console loss evidence |
126
- | `audit duplicates` | Stored duplicate clusters | Includes members and keep candidate |
127
- | `content briefs list` | Content Brief summaries | `--status`, `--limit`, `--offset` |
128
- | `content briefs show <brief-id>` | One Content Brief | Includes its grounded payload |
129
- | `content briefs create <keyword>` | – | Mutation. `--target-page` is optional |
130
- | `config` | Local config path, API host, selected Site | Local only |
131
-
132
- `page inspect` and `page scan` take an absolute URL, for example
133
- `https://example.com/about`.
134
-
135
- `--help --json` returns a `CliHelp` value for one command level. It separates
136
- positional `arguments`, command-specific `localOptions`, inherited
137
- `globalOptions`, and `subcommands`. To learn a subcommand's own flags, ask it
138
- directly, for example `nuxtseo actions list --help --json`. Use this instead of
139
- guessing a flag, and prefer the table above for anything it already answers.
101
+ Parse JSON. Never scrape human output.
102
+
103
+ Every `--json` run prints one envelope. For example, `status` returns this,
104
+ abridged:
105
+
106
+ ```sh
107
+ nuxtseo status --site site_01JXYZ --json
108
+ ```
109
+
110
+ ```json
111
+ {
112
+ "data": {
113
+ "available": true,
114
+ "dataQuality": { "status": "complete", "reason": null },
115
+ "nextAction": { "kind": "action", "actionId": "act_01JXYZ" }
116
+ },
117
+ "meta": { "requestId": "req_01JXYZ", "version": "1.0" }
118
+ }
119
+ ```
120
+
121
+ Read `data.available` first; `false` means the first assessment has not run.
122
+ Read `data.dataQuality.status` before you quote the verdict. Pass
123
+ `data.nextAction.actionId` into `actions show`.
140
124
 
141
125
  ## The triage loop
142
126
 
143
- This is the sequence that turns CLI output into a code change:
144
-
145
- 1. `nuxtseo actions list --site <id> --json` gives ranked work with an ID, a
146
- diagnosis, an effort, and an affected page count. Check
147
- `evidence.freshness` per row: `verdict: "aged"` with a large `ageHours`
148
- means the evidence is old. Live-check a cheap sample before fixing, because
149
- the site may have moved on since the observation.
150
- 2. `nuxtseo actions show <action-id> --site <id> --json` gives the evidence:
151
- which pages, which finding type, and when it was observed.
152
- 3. Fix the cause in the repository. The evidence names URLs; map them back to
153
- routes, components, or config.
154
- 4. Deploy or preview the fix, then `nuxtseo page scan <url> --site <id> --yes
155
- --json` to ask for fresh scans of a page you changed.
156
- 5. `nuxtseo actions resolve <action-id> --site <id> --yes --json` claims the
157
- action. The server verifies it; the CLI does not mark anything fixed by
158
- itself.
159
-
160
- Resolve reads the action first and sends the current `artifactVersion` for you.
161
- Do not construct that field by hand. If the evidence moved under you, the
162
- command exits `5` with `stale_evidence`; re-run step 2 and decide again.
127
+ This sequence turns CLI output into a code change. Copy the checklist and track
128
+ your progress:
129
+
130
+ ```
131
+ Triage progress:
132
+ - [ ] 1. status: read the verdict and the Next Action
133
+ - [ ] 2. actions list: pick a ranked action, check its freshness
134
+ - [ ] 3. actions show: read the evidence behind it
135
+ - [ ] 4. Fix the cause in the repository
136
+ - [ ] 5. page scan: re-scan a page you changed
137
+ - [ ] 6. actions resolve: claim the action
138
+ - [ ] 7. annotations create: mark the day the fix shipped
139
+ ```
140
+
141
+ **Step 1. Read the Site verdict.**
142
+
143
+ ```sh
144
+ nuxtseo status --site <site-id> --json
145
+ ```
146
+
147
+ This is the cheapest orientation: the verdict, the single ranked Next Action,
148
+ and what changed recently. Read `dataQuality.status` before quoting it.
149
+ `degraded` means the assessment ran with proofs missing, so the verdict is real
150
+ but Provisional. `unavailable` means it has not run. If `nextAction.kind` is
151
+ `action`, its `actionId` goes straight into step 3.
152
+
153
+ **Step 2. Pick a ranked action.**
154
+
155
+ ```sh
156
+ nuxtseo actions list --site <site-id> --json
157
+ ```
158
+
159
+ Each row carries an ID, a diagnosis, an effort, and an affected page count.
160
+ Check `evidence.freshness` per row. If `verdict` is `aged` with a large
161
+ `ageHours`, live-check a cheap sample before fixing. The site may have moved on
162
+ since the observation.
163
+
164
+ **Step 3. Read the evidence.**
165
+
166
+ ```sh
167
+ nuxtseo actions show <action-id> --site <site-id> --json
168
+ ```
169
+
170
+ This returns which pages, which finding type, and when it was observed.
171
+
172
+ Two reads deepen this step when the action's own evidence is not enough:
173
+
174
+ - `nuxtseo page issues --action-id <action-id> --site <site-id> --json` returns
175
+ the raw observations behind that action: the exact URLs, status codes,
176
+ redirect targets, and for a Lighthouse row the failing selectors and DOM
177
+ snippets. These are the mutable observation store, never canonical action
178
+ membership.
179
+ - `nuxtseo scans show <scan-id> --site <site-id> --json` returns the failing
180
+ checks behind a Lighthouse score, so you can locate the cause without
181
+ re-scanning.
182
+
183
+ **Step 4. Fix the cause in the repository.**
184
+
185
+ The evidence names URLs. Map them back to routes, components, or config.
186
+
187
+ **Step 5. Re-scan a page you changed.**
188
+
189
+ Deploy or preview the fix first. Then ask for fresh Scans:
190
+
191
+ ```sh
192
+ nuxtseo page scan <url> --site <site-id> --yes --json
193
+ ```
194
+
195
+ **Step 6. Claim the action.**
196
+
197
+ ```sh
198
+ nuxtseo actions resolve <action-id> --site <site-id> --yes --json
199
+ ```
200
+
201
+ The server verifies it. The CLI never marks anything fixed by itself. Resolve
202
+ reads the action first and sends the current `artifactVersion` for you. Do not
203
+ construct that field by hand. If the evidence moved under you, the command
204
+ exits `5` with `stale_evidence`. Re-run step 3 and decide again.
205
+
206
+ When the fix is a deliberate removal rather than a repair, dismiss instead of
207
+ resolving:
208
+
209
+ ```sh
210
+ nuxtseo actions show <action-id> --site <site-id> --json # read artifactVersion
211
+ nuxtseo actions dismiss <action-id> --site <site-id> --artifact-version <v> --yes --json
212
+ ```
213
+
214
+ Dismiss is admitted only for a broken-page action with complete 404 or 410
215
+ evidence and no internal referrers. Anything else is rejected, so it cannot be
216
+ used to hide work.
217
+
218
+ **Step 7. Mark the day the fix shipped.**
219
+
220
+ ```sh
221
+ nuxtseo annotations create --site <site-id> --date "$(date -u +%F)" --title "Fixed canonicals on /docs" --yes --json
222
+ ```
223
+
224
+ The next traffic move then has a cause beside it.
225
+
226
+ ## Datasets that must not be conflated
227
+
228
+ Two datasets share the word "vitals":
229
+
230
+ - `performance` and `scans *` read Lighthouse **lab** Scans.
231
+ - `vitals summary`, `vitals trend`, and `vitals findings` read **field**
232
+ (real-user) data. `vitals` reads CrUX. `vitals findings` reads the Site's own
233
+ Web Analytics provider.
234
+
235
+ A lab score and a field p75 disagreeing is normal, not a fault.
236
+
237
+ Two commands inspect the same URL and answer different questions. `page inspect`
238
+ reads the NuxtSEO observation store. `search inspect` reads Google's index
239
+ verdict.
240
+
241
+ `status`, `performance`, and `vitals` answer different questions. `status` is
242
+ the verdict. `performance` is the lab score. `vitals` is what real users
243
+ measured. Never substitute one for another.
244
+
245
+ The Search Console reads also split:
246
+
247
+ - `search status` reads stored connection state. It never waits for Google.
248
+ - `search analytics`, `search indexing`, `search index-history`, and
249
+ `search inspect` read retained Search Console evidence through the public API.
250
+ - `sitemaps submit` and `sitemaps delete` reach Google through the Site's
251
+ stored Search Console credential. Both are mutations.
252
+
253
+ ## Route-family indexing: two lists, one claim
254
+
255
+ `search cohorts` answers "which template is Google declining" in one read,
256
+ instead of N page-level findings. Read it before you list individual
257
+ not-indexed URLs. Its output holds two lists that mean different things. Never
258
+ merge them.
259
+
260
+ - `established` holds only families whose Wilson score interval clears the
261
+ not-indexed rate of their **own complement** after a Bonferroni correction.
262
+ These are the only rows you may report as a finding or a cause.
263
+ - `ranked` orders every family by raw rate, with **no significance test**. It
264
+ always answers "where is indexing worst", even when nothing is provable. A
265
+ row with `statisticallyEstablished: false` is a lead to verify, never a
266
+ conclusion. Presenting one as established is a hard failure.
267
+
268
+ `analysed: false` means there is no completed crawl, or no Google index state
269
+ to join against. That is a coverage gap, not healthy indexing. Say which.
270
+
271
+ ## Report CLI feedback
272
+
273
+ If CLI behavior blocks or slows your work, submit feedback once per distinct problem during the task.
274
+ Report confusing output, missing context, incorrect help, unexpected failures, and useful workarounds.
275
+ Continue the user's task after reporting.
276
+
277
+ ```sh
278
+ nuxtseo feedback submit --command "search cohorts" --agent "Codex" \
279
+ --comment "Agent report by Codex. Expected a reason for missing data. Received an empty list. Used search status instead." \
280
+ --yes --json
281
+ ```
282
+
283
+ Use your actual agent name. Start the comment with an agent disclosure.
284
+ Include reproduction steps, expected behavior, actual behavior, and any workaround.
285
+ Use `--intent improvement` for suggestions. The default is `bug`.
286
+ If the affected response has a request ID, pass it with `--request-id`.
287
+ The CLI adds its version automatically.
288
+
289
+ Send sanitized details only. Remove tokens, cookies, personal data, private URLs, and customer content.
290
+ Use command names and placeholder arguments. Never paste raw logs or full response bodies.
291
+
292
+ Self-reporting sanitized CLI feedback is authorized by this skill. No separate confirmation is needed.
293
+ This permission covers feedback only. Respect any user instruction that forbids reporting.
294
+ The endpoint requires a working credential with `feedback:write`, available to every Team role.
295
+ It uses no paid limit and allows ten reports per credential each hour.
296
+
297
+ Success returns `data.id` and `data.status: "new"`. Keep the ID with your task notes.
298
+ If reporting fails, mention the failure and continue. Never retry uncertain submissions or report failures recursively.
299
+ If authentication is broken, describe the feedback to the user. Do not invent another endpoint.
300
+
301
+ Reports use the existing feedback queue through `POST /api/v1/account/feedback`.
302
+ Health checks show unresolved reports across all dates.
303
+ Operators use **Pro feedback** in admin to record a fix reference and set `fixed` after verification.
304
+ Fixed, replied, closed, and ignored reports leave the health check list. Reopening restores them.
163
305
 
164
306
  ## Guardrails
165
307
 
166
- - Ask before you mutate. `actions resolve` and `page scan` consume quota and
167
- change server state. `--yes` is consent you are borrowing from the user, so
168
- get it first unless the user already asked for that exact action.
169
- - Do not loop over Pages, keywords, or domains unattended. Check `usage` first.
170
- - Live research can consume allowance. A cached result does not consume a unit.
171
- - Report failures as they are. The CLI has no MCP or private-route fallback.
172
- - Do not treat an empty result as clean. `page inspect` JSON says which empty
173
- it is through `observations.coverage`; other commands may still have
174
- incomplete evidence.
308
+ - **Get consent before a mutation.** `actions resolve`, `actions dismiss`,
309
+ `page scan`, `sitemaps submit`, `sitemaps delete`, `content briefs create`,
310
+ and the `annotations` writes all change server state. `page scan` also spends
311
+ against the Lighthouse limit. `--yes` is consent you borrow from the user, so
312
+ ask first, unless the user already asked for that exact action. `actions
313
+ dismiss` says the page stays removed, so use it only when that is the
314
+ decision.
315
+ - **Live research spends against the Team research limit.** `research
316
+ keywords`, `research serp`, `research rankings`, `research domain-traffic`,
317
+ `research domain-availability`, and the `backlinks` reads other than
318
+ `backlinks recoverable` can all start it. Each one reports what it did.
319
+ Keyword, domain, and backlink JSON report cache use in `evidence`; a `cache`
320
+ or `no-provider` tag means nothing was spent. SERP and ranking JSON report
321
+ `cached`. `backlinks recoverable` and `mentions list` read retained rows and
322
+ spend nothing.
323
+ - **Never loop unattended.** Check `usage` before any run over Pages, keywords,
324
+ or domains. Use `--all` rather than your own offset loop. It writes one
325
+ envelope per line and stops at 50 requests with exit `9`. Treat exit `9` as a
326
+ stop, then resume with the argument stderr names.
327
+ - **Report the result as the CLI gave it.** Report failures as they are; there
328
+ is no MCP or private-route fallback. Never treat an empty result as clean:
329
+ `page inspect` names the kind of empty through `observations.coverage`,
330
+ `page issues` through `availableKeys`, `search cohorts` through
331
+ `analysed: false`, and `vitals summary` through `available: false`. A refusal
332
+ reads differently again: `status`, `page issues` and `search cohorts` exit
333
+ `4` or `5` on an archived or paused Site, which is never an empty Site. Other
334
+ commands may still return Provisional evidence. Quote the counts a result
335
+ ships with, never the length of its list: `data.total` from `page issues`,
336
+ then `coverage.accessibilityChecksTotal` and
337
+ `coverage.bestPracticesChecksTotal` from `scans show`. Neither command
338
+ reports the dropped row count, so `possiblyTruncated: true` marks the list as
339
+ a floor.