@nuxtseo/cli 0.2.1 → 0.4.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.
@@ -1,4 +1,5 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { checkSkillVersion, skillNoticeLine } from './skill.js';
2
3
  import { VERSION } from './version.js';
3
4
  /** How long a registry answer stays authoritative, matching npm's update cache. */
4
5
  export const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
@@ -85,13 +86,28 @@ export async function fetchRegistryLatest(url, signal) {
85
86
  const version = body.version;
86
87
  return typeof version === 'string' && version ? version : null;
87
88
  }
89
+ async function emitSkillNotice(check, onDiagnostic) {
90
+ const notice = await check.catch(() => {
91
+ // A local read failure must never break a command. The run continues silently.
92
+ return null;
93
+ });
94
+ if (notice && onDiagnostic)
95
+ onDiagnostic(skillNoticeLine(notice));
96
+ }
88
97
  export async function checkForUpdate(options) {
89
98
  if (updateCheckDisabled(options.env))
90
99
  return null;
100
+ // The installed agent skill is the other half of "is this install current".
101
+ // It runs beside the registry fetch so neither waits for the other.
102
+ const skillCheck = options.onDiagnostic
103
+ ? checkSkillVersion({ paths: options.paths, env: options.env, now: options.now })
104
+ : Promise.resolve(null);
91
105
  const now = options.now?.() ?? new Date();
92
106
  const cache = await readUpdateCheckCache(options.paths);
93
- if (!updateCheckDue(cache, now))
107
+ if (!updateCheckDue(cache, now)) {
108
+ await emitSkillNotice(skillCheck, options.onDiagnostic);
94
109
  return updateNoticeFor(cache.latest, VERSION);
110
+ }
95
111
  const fetchLatest = options.fetch ?? fetchRegistryLatest;
96
112
  const latest = await fetchLatest(REGISTRY_LATEST_URL, AbortSignal.timeout(FETCH_TIMEOUT_MS)).catch(() => {
97
113
  // Registry reachability is best effort. The run continues without a hint.
@@ -99,6 +115,7 @@ export async function checkForUpdate(options) {
99
115
  });
100
116
  if (latest !== null)
101
117
  await writeUpdateCheckCache(options.paths, { lastCheckedAt: now.toISOString(), latest });
118
+ await emitSkillNotice(skillCheck, options.onDiagnostic);
102
119
  // A stale cached answer still beats no answer while the registry is unreachable.
103
120
  return updateNoticeFor(latest ?? cache?.latest ?? null, VERSION);
104
121
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@nuxtseo/cli",
3
3
  "type": "module",
4
- "version": "0.2.1",
5
- "description": "Command line interface for the NuxtSEO public API.",
4
+ "version": "0.4.0",
5
+ "description": "Command line interface for the Nuxt SEO public API.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://nuxtseo.com/pro",
8
8
  "repository": {
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@clack/prompts": "^1.8.0",
36
- "@nuxtseo/protocol": "^0.2.1",
37
- "@nuxtseo/sdk": "^0.2.1",
36
+ "@nuxtseo/protocol": "^0.4.0",
37
+ "@nuxtseo/sdk": "^0.4.0",
38
38
  "citty": "^0.2.2",
39
39
  "pathe": "^2.0.3"
40
40
  },
@@ -1,110 +1,98 @@
1
1
  ---
2
2
  name: nuxtseo-cli
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.
3
+ description: Drives the `nuxtseo` CLI to triage a Site through the Nuxt SEO 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 decay, duplicate clusters, chart annotations, and issue resolution. Use whenever the user mentions NuxtSEO, the `nuxtseo` command, Site SEO work, or Nuxt SEO automation.
4
4
  ---
5
5
 
6
- # NuxtSEO CLI
6
+ # `nuxtseo` CLI
7
7
 
8
- `nuxtseo` reads a Site through the NuxtSEO public API. Every command goes
9
- through `@nuxtseo/sdk`. The CLI never calls MCP or private routes. Live
10
- research reaches a provider only through a declared public API operation.
11
-
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.
8
+ `nuxtseo` reads a Site through the Nuxt SEO public API. Use it to answer "what
9
+ is wrong with this site, and what did I break", then fix the cause in the
10
+ repository you are working in. There is no MCP or private-route fallback.
14
11
 
15
12
  ## Reference files
16
13
 
17
- Read each one before you act on its subject:
18
-
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.
26
-
27
- ## Get the binary
28
-
29
- ```sh
30
- pnpm dlx @nuxtseo/cli@latest --version # no install
31
- pnpm add -g @nuxtseo/cli # or npm install --global
32
- ```
33
-
34
- Node 22 or newer is required.
35
-
36
- Inside the NuxtSEO monorepo the package is not published. Run the built entry
37
- directly:
14
+ Read the matching file before you act on its subject:
38
15
 
39
- ```sh
40
- node packages/cli/dist/cli-entry.js --version
41
- ```
16
+ | File | Read it before you |
17
+ | --- | --- |
18
+ | [references/commands.md](references/commands.md) | use a flag, range, or default. Never guess a flag |
19
+ | [references/protocol.md](references/protocol.md) | parse output, page through a list, or handle an exit code |
20
+ | [references/indexing.md](references/indexing.md) | report an indexed count or name indexed URLs |
21
+
22
+ ## Setup
23
+
24
+ 1. **Binary.** Exit `127` means the CLI is absent. Install it, then continue:
25
+
26
+ ```sh
27
+ pnpm dlx @nuxtseo/cli@latest --version # no install
28
+ pnpm add -g @nuxtseo/cli # global install, Node 22+
29
+ ```
30
+
31
+ Inside the Nuxt SEO monorepo, run `node packages/cli/dist/cli-entry.js`
32
+ instead.
33
+
34
+ 2. **Skill version.** Run `nuxtseo --version --json`. If stderr carries a line
35
+ like `skill 0.3.0 installed, CLI 0.4.0. Refresh with: nuxtseo skill install
36
+ --agent claude`, run that command and re-read this file. A stale skill hides
37
+ commands, so a missing command reads as a missing feature. If
38
+ `NUXTSEO_NO_UPDATE_CHECK` is set, the line never appears; compare this
39
+ file's frontmatter `version` with the binary instead.
40
+
41
+ 3. **Token.** Run `nuxtseo whoami --json`. It returns the Team, role, scopes,
42
+ and token expiry. Exit `3` means no working token. Ask the user to run
43
+ `nuxtseo login` themselves, because a person must approve it in a browser.
44
+ The other route is a token from
45
+ <https://nuxtseo.com/pro/dashboard/settings/api-tokens>, exported as
46
+ `NUXTSEO_TOKEN`. Never put a token in an argument, a file, or a commit.
47
+
48
+ 4. **Scopes.** Read `data.scopes` now, not when a step fails. A `viewer` token
49
+ carries every `*:read` scope plus `feedback:write`. It can run every read,
50
+ including live research, and no write:
51
+
52
+ | Write | Scope |
53
+ | --- | --- |
54
+ | `page scan` | `page:write` |
55
+ | `actions resolve`, `actions dismiss` | `actions:write` |
56
+ | `annotations` writes | `timeline:write` |
57
+ | `sitemaps submit`, `sitemaps delete` | `sites:write` |
58
+
59
+ If a write scope is missing, do the reads, then hand the user the exact
60
+ write commands. Do not discover the block one exit `4` at a time.
61
+
62
+ 5. **Site.** Run `nuxtseo sites list --json` once and reuse the ID. If more
63
+ than one Site is accessible and `--site` is absent, the CLI exits `5`
64
+ rather than guess.
65
+
66
+ ## Calling convention
67
+
68
+ - Always pass `--json` and `--site <site-id>`. `feedback submit` needs no Site.
69
+ - `--json` writes one envelope to stdout. Diagnostics go to stderr. Parse the
70
+ JSON; never scrape human output.
71
+ - A mutation needs `--yes`. Without it, a non-interactive run exits `2`.
72
+ - `--timeout-ms` raises the 30000 ms deadline, up to 300000.
73
+ - An unknown option exits `2` before any network work.
74
+ - Branch on the exit code, never on message text. The table is in
75
+ [references/protocol.md](references/protocol.md).
42
76
 
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.
77
+ ## The triage loop
45
78
 
46
- ## Before the first command
79
+ Copy this checklist and track your progress:
47
80
 
48
- ```sh
49
- nuxtseo whoami --json # validates the token and reports its scopes
50
81
  ```
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
-
56
- Exit `3` means there is no working token. Ask the user to run `nuxtseo login`
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.
60
-
61
- The alternative is a token created at
62
- <https://nuxtseo.com/pro/dashboard/settings/api-tokens> and exported as
63
- `NUXTSEO_TOKEN`. Either way the token is bound to a Team role, and that role
64
- gates what it can do.
65
-
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.
68
-
69
- Resolve the Site once and reuse it. `nuxtseo sites list --json` prints every
70
- accessible Site with its ID.
71
-
72
- ## How to call it as an agent
73
-
74
- Always pass `--json`. Pass `--site <site-id>` for Site commands.
75
- `feedback submit` needs no Site.
76
-
77
- ```sh
78
- nuxtseo actions list --site site_123 --json
82
+ Triage progress:
83
+ - [ ] 1. status: read the verdict and the Next Action
84
+ - [ ] 2. actions list: pick a ranked action, check its freshness
85
+ - [ ] 3. actions show: read the evidence behind it
86
+ - [ ] 4. Fix the cause in the repository
87
+ - [ ] 5. page scan: re-scan a page you changed
88
+ - [ ] 6. actions resolve: claim the action
89
+ - [ ] 7. annotations create: mark the day the fix shipped
79
90
  ```
80
91
 
81
- `--json` writes the complete protocol envelope to stdout and one newline. It
82
- also disables prompts. Diagnostics, warnings, spinners, and failure messages go
83
- to stderr, so stdout stays parseable.
84
-
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
88
- exits `5` rather than guessing.
89
-
90
- Other flags worth knowing:
91
-
92
- | Flag | Use it when |
93
- | --- | --- |
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 |
98
-
99
- Unknown options exit `2` before any network work, so a typo costs nothing.
100
-
101
- Parse JSON. Never scrape human output.
102
-
103
- Every `--json` run prints one envelope. For example, `status` returns this,
104
- abridged:
92
+ **1. Read the verdict.**
105
93
 
106
94
  ```sh
107
- nuxtseo status --site site_01JXYZ --json
95
+ nuxtseo status --site <site-id> --json
108
96
  ```
109
97
 
110
98
  ```json
@@ -118,104 +106,73 @@ nuxtseo status --site site_01JXYZ --json
118
106
  }
119
107
  ```
120
108
 
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`.
124
-
125
- ## The triage loop
126
-
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
- ```
109
+ - `available: false` means the first assessment has not run.
110
+ - Read `dataQuality.status` before you quote the verdict. `degraded` means
111
+ proofs were missing, so the verdict is Provisional. `unavailable` means it
112
+ has not run.
113
+ - If `nextAction.kind` is `action`, pass its `actionId` to step 3.
140
114
 
141
- **Step 1. Read the Site verdict.**
115
+ For the whole Site instead of the next action, run one `pull`. It performs
116
+ every spend-free Site read and writes one envelope per line:
142
117
 
143
118
  ```sh
144
- nuxtseo status --site <site-id> --json
119
+ nuxtseo pull --site <site-id> --json > site.ndjson
145
120
  ```
146
121
 
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.**
122
+ **2. Pick a ranked action.**
154
123
 
155
124
  ```sh
156
125
  nuxtseo actions list --site <site-id> --json
157
126
  ```
158
127
 
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.
128
+ Keep server order. Check `evidence.freshness` on each row. If `verdict` is
129
+ `aged` with a large `ageHours`, live-check a cheap sample before you fix. The
130
+ Site may have changed since the observation.
163
131
 
164
- **Step 3. Read the evidence.**
132
+ **3. Read the evidence.**
165
133
 
166
134
  ```sh
167
135
  nuxtseo actions show <action-id> --site <site-id> --json
168
136
  ```
169
137
 
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:
138
+ It names the pages, the finding type, and when it was observed. Two reads go
139
+ deeper:
173
140
 
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.
141
+ - `page issues --action-id <action-id>`: the raw observations, with URLs,
142
+ status codes, redirect targets, and Lighthouse selectors. This is the
143
+ mutable observation store, never canonical action membership.
144
+ - `scans show <scan-id>`: the failing checks behind a Lighthouse score.
182
145
 
183
- **Step 4. Fix the cause in the repository.**
146
+ **4. Fix the cause.** Map the URLs in the evidence to routes, components, or
147
+ config.
184
148
 
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:
149
+ **5. Re-scan.** Deploy or preview the fix first:
190
150
 
191
151
  ```sh
192
152
  nuxtseo page scan <url> --site <site-id> --yes --json
193
153
  ```
194
154
 
195
- **Step 6. Claim the action.**
155
+ **6. Claim the action.**
196
156
 
197
157
  ```sh
198
158
  nuxtseo actions resolve <action-id> --site <site-id> --yes --json
199
159
  ```
200
160
 
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.
161
+ The server verifies the claim; the CLI never marks anything fixed. Resolve
162
+ sends the current `artifactVersion` for you. If the evidence moved, it exits
163
+ `5` with `stale_evidence`: re-run step 3 and decide again.
205
164
 
206
- When the fix is a deliberate removal rather than a repair, dismiss instead of
207
- resolving:
165
+ If the fix is a deliberate removal, dismiss instead. Read `artifactVersion`
166
+ from `actions show`, then:
208
167
 
209
168
  ```sh
210
- nuxtseo actions show <action-id> --site <site-id> --json # read artifactVersion
211
169
  nuxtseo actions dismiss <action-id> --site <site-id> --artifact-version <v> --yes --json
212
170
  ```
213
171
 
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.
172
+ The server admits a dismiss only for a broken-page action with complete 404 or
173
+ 410 evidence and no internal referrers.
217
174
 
218
- **Step 7. Mark the day the fix shipped.**
175
+ **7. Mark the day.**
219
176
 
220
177
  ```sh
221
178
  nuxtseo annotations create --site <site-id> --date "$(date -u +%F)" --title "Fixed canonicals on /docs" --yes --json
@@ -223,117 +180,93 @@ nuxtseo annotations create --site <site-id> --date "$(date -u +%F)" --title "Fix
223
180
 
224
181
  The next traffic move then has a cause beside it.
225
182
 
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.
183
+ ## Reading results correctly
252
184
 
253
- ## Route-family indexing: two lists, one claim
185
+ **Empty is not clean.** Each read names its own kind of empty. Say which one
186
+ you got:
254
187
 
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.
188
+ | Command | Empty signal |
189
+ | --- | --- |
190
+ | `page inspect` | `observations.coverage` |
191
+ | `page issues` | `availableKeys` |
192
+ | `search cohorts` | `analysed: false`: no completed crawl, or no Google index state |
193
+ | `vitals summary` | `available: false` |
194
+ | `research keywords`, `research rankings`, `research domain-*`, `page issues`, `search cohorts`, `vitals findings` | `data.message`, and `data.tip` for the repair |
195
+
196
+ Read `data.message` before the list. For example, `research keywords` refuses
197
+ a seed over three words, still exits `0`, and returns `keywords: []` with
198
+ `evidence._tag: "no-provider"`. That is a refused argument, not zero demand.
199
+
200
+ **A refusal is not an empty Site.** Exit `4` is a plan, scope, or entitlement
201
+ blocker, for example `entitlement_required`. Exit `5` on `status`, `page
202
+ issues`, or `search cohorts` can mean an archived or paused Site. Report the
203
+ blocker and stop. Do not read it as "no data" or "nothing wrong", and do not
204
+ retry unchanged.
205
+
206
+ **Quote the count the result ships.** Never quote the length of a list. Use
207
+ `data.total` from `page issues`, and `coverage.accessibilityChecksTotal` and
208
+ `coverage.bestPracticesChecksTotal` from `scans show`. `possiblyTruncated:
209
+ true` marks the list as a floor.
210
+
211
+ **Keep these datasets apart:**
212
+
213
+ - `performance` and `scans *` read Lighthouse **lab** Scans. `vitals summary`
214
+ and `vitals trend` read CrUX **field** data. `vitals findings` reads the
215
+ Site's own Web Analytics provider. A lab score and a field p75 that disagree
216
+ is normal.
217
+ - `status` is the verdict, `performance` the lab score, `vitals` what real
218
+ users measured. Never substitute one for another.
219
+ - `page inspect` reads the Nuxt SEO observation store. `search inspect` reads
220
+ Google's index verdict for the same URL.
221
+ - `search status` reads the stored connection and never waits for Google.
222
+ `search analytics`, `search indexing`, `search index-history`, and
223
+ `search inspect` read retained Search Console evidence.
224
+ - An indexed count comes from `search indexing`, never from analytics rows.
225
+ See [references/indexing.md](references/indexing.md).
226
+
227
+ **Route families: `established` versus `ranked`.** Read `search cohorts`
228
+ before you list single not-indexed URLs. Never merge its two lists:
229
+
230
+ - `established` holds only families whose not-indexed rate is significant
231
+ against the rest of the Site, after a Bonferroni correction. Only these rows
232
+ are a finding or a cause.
233
+ - `ranked` orders every family by raw rate, with no significance test. A row
234
+ with `statisticallyEstablished: false` is a lead to verify, for example by
235
+ inspecting its URLs. Presenting it as a cause is a hard failure.
259
236
 
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.
237
+ ## Guardrails
267
238
 
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.
239
+ - **Get consent before a mutation.** `page scan`, `actions resolve`, `actions
240
+ dismiss`, `sitemaps submit`, `sitemaps delete`, and the `annotations` writes
241
+ change server state. `sitemaps` writes also reach Google. `--yes` is consent
242
+ you borrow from the user, so ask first unless they asked for that exact
243
+ action.
244
+ - **Spend limits.** `page scan` spends Lighthouse limit. `research *` and the
245
+ `backlinks` reads spend the Team research limit, except `backlinks
246
+ recoverable` and `mentions list`, which read retained rows. A `cache` or
247
+ `no-provider` evidence tag, or `cached: true`, means nothing was spent.
248
+ Check `usage` before any run over many Pages, keywords, or domains.
249
+ - **No shell loops.** Use `pull` for Site reads and `--all` for paging. `--all`
250
+ stops at 50 requests with exit `9`; treat that as a stop and resume with the
251
+ argument stderr names.
270
252
 
271
253
  ## Report CLI feedback
272
254
 
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.
255
+ If the CLI blocks or slows your work, report it once per distinct problem, then
256
+ continue the task. This skill authorizes that; no confirmation is needed unless
257
+ the user forbids reporting.
276
258
 
277
259
  ```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." \
260
+ nuxtseo feedback submit --command "search cohorts" --agent "<your agent name>" \
261
+ --comment "Agent report by <your agent name>. Expected a reason for missing data. Received an empty list. Used search status instead." \
280
262
  --yes --json
281
263
  ```
282
264
 
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.
305
-
306
- ## Guardrails
307
-
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.
265
+ - Start the comment with an agent disclosure. Give reproduction steps, the
266
+ expected and actual behaviour, and any workaround.
267
+ - Pass `--request-id` when the response had one. Use `--intent improvement`
268
+ for a suggestion; the default is `bug`.
269
+ - Send sanitized details only: no tokens, cookies, personal data, private URLs,
270
+ customer content, raw logs, or full response bodies.
271
+ - The limit is ten reports per credential per hour. If a report fails, say so
272
+ and continue. Never retry it or report the failure.