@nuxtseo/cli 0.2.0 → 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.
package/dist/cli.js CHANGED
@@ -126,7 +126,7 @@ async function switchSite(runtime, globals) {
126
126
  output: runtime.error,
127
127
  signal: runtime.signal,
128
128
  });
129
- if (prompts.isCancel(selected))
129
+ if (typeof selected === 'symbol')
130
130
  return fail(EXIT_CODE.interrupted, 'interrupted: Site selection cancelled.');
131
131
  const saved = await updateConfig({ siteId: selected }, { paths: runtime.paths });
132
132
  if (saved._tag === 'Err')
@@ -143,7 +143,7 @@ async function promptPageUrl(runtime, action) {
143
143
  signal: runtime.signal,
144
144
  validate: value => typeof value === 'string' && URL.canParse(value) ? undefined : 'Enter an absolute URL.',
145
145
  });
146
- return prompts.isCancel(url)
146
+ return typeof url === 'symbol'
147
147
  ? fail(EXIT_CODE.interrupted, `interrupted: ${action} cancelled.`)
148
148
  : ok(url);
149
149
  }
@@ -158,7 +158,7 @@ async function promptTextValue(runtime, options) {
158
158
  ? undefined
159
159
  : `${options.label} is required.`,
160
160
  });
161
- return prompts.isCancel(value)
161
+ return typeof value === 'symbol'
162
162
  ? fail(EXIT_CODE.interrupted, `interrupted: ${options.label} entry cancelled.`)
163
163
  : ok(value.trim());
164
164
  }
@@ -219,7 +219,7 @@ async function interactiveBare(runtime, globals) {
219
219
  output: runtime.error,
220
220
  signal: runtime.signal,
221
221
  });
222
- if (prompts.isCancel(group) || group === 'exit')
222
+ if (typeof group === 'symbol' || group === 'exit')
223
223
  return ok(undefined);
224
224
  if (group === 'login')
225
225
  return runInteractiveCommand(['login'], runtime, globals);
@@ -239,7 +239,7 @@ async function interactiveBare(runtime, globals) {
239
239
  output: runtime.error,
240
240
  signal: runtime.signal,
241
241
  });
242
- if (prompts.isCancel(task))
242
+ if (typeof task === 'symbol')
243
243
  return ok(undefined);
244
244
  if (task === 'inspect' || task === 'scan') {
245
245
  const url = await promptPageUrl(runtime, task === 'inspect' ? 'Inspect' : 'Scan');
@@ -278,7 +278,7 @@ async function interactiveBare(runtime, globals) {
278
278
  output: runtime.error,
279
279
  signal: runtime.signal,
280
280
  });
281
- if (prompts.isCancel(task))
281
+ if (typeof task === 'symbol')
282
282
  return ok(undefined);
283
283
  const command = task === 'performance'
284
284
  ? ['performance']
@@ -318,7 +318,7 @@ async function interactiveBare(runtime, globals) {
318
318
  output: runtime.error,
319
319
  signal: runtime.signal,
320
320
  });
321
- if (prompts.isCancel(task))
321
+ if (typeof task === 'symbol')
322
322
  return ok(undefined);
323
323
  if (task === 'overview')
324
324
  return runInteractiveCommand(['research', 'overview'], runtime, globals);
@@ -362,7 +362,7 @@ async function interactiveBare(runtime, globals) {
362
362
  output: runtime.error,
363
363
  signal: runtime.signal,
364
364
  });
365
- if (prompts.isCancel(task))
365
+ if (typeof task === 'symbol')
366
366
  return ok(undefined);
367
367
  if (task === 'briefs')
368
368
  return runInteractiveCommand(['content', 'briefs', 'list'], runtime, globals);
@@ -394,7 +394,7 @@ async function interactiveBare(runtime, globals) {
394
394
  output: runtime.error,
395
395
  signal: runtime.signal,
396
396
  });
397
- if (prompts.isCancel(task))
397
+ if (typeof task === 'symbol')
398
398
  return ok(undefined);
399
399
  if (task === 'site')
400
400
  return switchSite(runtime, globals);
package/dist/commands.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as prompts from '@clack/prompts';
2
+ import { accountFeedbackBodySchema } from '@nuxtseo/protocol/v1/account';
2
3
  import { publicV1BooleanFlagValue } from '@nuxtseo/protocol/v1/core';
3
4
  import { defineCommand } from 'citty';
4
5
  import { dirname } from 'pathe';
@@ -97,7 +98,7 @@ async function confirmMutation(runtime, globals, message) {
97
98
  signal: runtime.signal,
98
99
  initialValue: false,
99
100
  });
100
- if (prompts.isCancel(confirmed))
101
+ if (typeof confirmed === 'symbol')
101
102
  return fail(EXIT_CODE.interrupted, 'interrupted: Mutation cancelled.');
102
103
  return ok(confirmed);
103
104
  }
@@ -111,7 +112,7 @@ async function readLoginToken(runtime) {
111
112
  signal: runtime.signal,
112
113
  validate: value => typeof value === 'string' && value.trim() ? undefined : 'Token is required.',
113
114
  });
114
- if (prompts.isCancel(token))
115
+ if (typeof token === 'symbol')
115
116
  return fail(EXIT_CODE.interrupted, 'interrupted: Login cancelled.');
116
117
  return ok(token.trim());
117
118
  }
@@ -277,7 +278,7 @@ async function config(runtime, globals) {
277
278
  output: runtime.error,
278
279
  signal: runtime.signal,
279
280
  });
280
- if (prompts.isCancel(action) || action === 'done')
281
+ if (typeof action === 'symbol' || action === 'done')
281
282
  return ok(undefined);
282
283
  if (action === 'api') {
283
284
  const next = await prompts.text({
@@ -287,7 +288,7 @@ async function config(runtime, globals) {
287
288
  output: runtime.error,
288
289
  signal: runtime.signal,
289
290
  });
290
- if (prompts.isCancel(next))
291
+ if (typeof next === 'symbol')
291
292
  return fail(EXIT_CODE.interrupted, 'interrupted: Configuration cancelled.');
292
293
  const written = await updateConfig({ apiUrl: next }, { paths: runtime.paths });
293
294
  if (written._tag === 'Err')
@@ -345,6 +346,25 @@ async function usage(runtime, globals, group) {
345
346
  const response = await withSpinner(runtime, 'Loading usage', () => api.value.client.account.usage({ query: { group } }, { signal: runtime.requestSignal }));
346
347
  return present(runtime, globals, response, renderUsage);
347
348
  }
349
+ async function feedbackSubmit(runtime, globals, args) {
350
+ const body = accountFeedbackBodySchema.safeParse({ ...args, cliVersion: VERSION });
351
+ if (!body.success)
352
+ return fail(EXIT_CODE.invalidInput, 'Pass valid --command, --comment, and --agent values. Read feedback submit --help for limits.', undefined, 'invalid_cli_input');
353
+ const confirmed = await confirmMutation(runtime, globals, 'Submit this agent report to NuxtSEO feedback?');
354
+ if (confirmed._tag === 'Err')
355
+ return confirmed;
356
+ if (!confirmed.value) {
357
+ writeDiagnostic(runtime, 'Feedback cancelled.');
358
+ return ok(undefined);
359
+ }
360
+ const api = await loadApiContext(runtime, globals);
361
+ if (api._tag === 'Err')
362
+ return api;
363
+ const response = await withSpinner(runtime, 'Submitting feedback', () => api.value.client.account.feedback({
364
+ body: body.data,
365
+ }, { signal: runtime.requestSignal }));
366
+ return present(runtime, globals, response, value => `Feedback saved: ${value.data.id} (${value.data.status})`);
367
+ }
348
368
  async function actionsList(runtime, globals, args) {
349
369
  const limit = parseInteger(args.limit, { name: '--limit', minimum: 1, maximum: 25, defaultValue: 10 });
350
370
  if (limit._tag === 'Err')
@@ -1877,8 +1897,8 @@ export function createRootCommand(runtime, globals, execution) {
1877
1897
  'changes': defineCommand({
1878
1898
  meta: { name: 'changes', description: 'Compare the latest crawl with its baseline' },
1879
1899
  args: {
1880
- from: { type: 'string', description: 'Baseline crawl generation' },
1881
- to: { type: 'string', description: 'Comparison crawl generation' },
1900
+ from: { type: 'string', description: 'Baseline crawl number' },
1901
+ to: { type: 'string', description: 'Comparison crawl number' },
1882
1902
  },
1883
1903
  run: ({ args }) => capture(execution, () => auditChanges(runtime, globals, {
1884
1904
  from: args.from,
@@ -2155,6 +2175,28 @@ export function createRootCommand(runtime, globals, execution) {
2155
2175
  run: ({ args }) => capture(execution, () => config(runtime, globals), args._, 0)(),
2156
2176
  }),
2157
2177
  sites,
2178
+ feedback: defineCommand({
2179
+ meta: { name: 'feedback', description: 'Report CLI problems to NuxtSEO' },
2180
+ subCommands: {
2181
+ submit: defineCommand({
2182
+ meta: { name: 'submit', description: 'Submit sanitized agent feedback without selecting a Site' },
2183
+ args: {
2184
+ 'command': { type: 'string', required: true, description: 'Affected command without secrets or private arguments, maximum 200 characters' },
2185
+ 'comment': { type: 'string', required: true, description: 'Agent disclosure, reproduction steps, expected result, actual result, and workaround, maximum 2000 characters' },
2186
+ 'agent': { type: 'string', required: true, description: 'Reporting agent name, maximum 100 characters' },
2187
+ 'intent': { type: 'enum', options: ['bug', 'improvement'], description: 'Feedback intent, default bug' },
2188
+ 'request-id': { type: 'string', description: 'Request ID from the affected response, maximum 128 characters' },
2189
+ },
2190
+ run: ({ args }) => capture(execution, () => feedbackSubmit(runtime, globals, {
2191
+ command: args.command,
2192
+ comment: args.comment,
2193
+ agent: args.agent,
2194
+ intent: args.intent,
2195
+ requestId: args['request-id'],
2196
+ }), args._, 0)(),
2197
+ }),
2198
+ },
2199
+ }),
2158
2200
  usage: defineCommand({
2159
2201
  meta: { name: 'usage', description: 'Read account usage' },
2160
2202
  args: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxtseo/cli",
3
3
  "type": "module",
4
- "version": "0.2.0",
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",
@@ -32,8 +32,9 @@
32
32
  "node": ">=22"
33
33
  },
34
34
  "dependencies": {
35
- "@clack/prompts": "^1.7.0",
36
- "@nuxtseo/sdk": "^0.2.0",
35
+ "@clack/prompts": "^1.8.0",
36
+ "@nuxtseo/protocol": "^0.2.1",
37
+ "@nuxtseo/sdk": "^0.2.1",
37
38
  "citty": "^0.2.2",
38
39
  "pathe": "^2.0.3"
39
40
  },
@@ -42,10 +43,9 @@
42
43
  },
43
44
  "devDependencies": {
44
45
  "@arethetypeswrong/cli": "^0.18.5",
45
- "@nuxtseo/protocol": "0.2.0",
46
- "@types/node": "^26.4.0",
46
+ "@types/node": "^26.5.1",
47
47
  "publint": "^0.3.24",
48
- "typescript": "npm:typescript-native-bridge@6.0.3-bridge.15.tsgo.7.0.2"
48
+ "typescript": "npm:typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"
@@ -71,7 +71,8 @@ accessible Site with its ID.
71
71
 
72
72
  ## How to call it as an agent
73
73
 
74
- Always pass `--json` and `--site <site-id>`.
74
+ Always pass `--json`. Pass `--site <site-id>` for Site commands.
75
+ `feedback submit` needs no Site.
75
76
 
76
77
  ```sh
77
78
  nuxtseo actions list --site site_123 --json
@@ -267,6 +268,41 @@ merge them.
267
268
  `analysed: false` means there is no completed crawl, or no Google index state
268
269
  to join against. That is a coverage gap, not healthy indexing. Say which.
269
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.
305
+
270
306
  ## Guardrails
271
307
 
272
308
  - **Get consent before a mutation.** `actions resolve`, `actions dismiss`,
@@ -37,6 +37,7 @@ indexing urls`, `sitemaps urls` and `content briefs list`.
37
37
  | Command | What it returns, and its flags |
38
38
  | --- | --- |
39
39
  | `whoami` | Credential, Team, role, granted scopes, token expiry. The cheapest health check. Reads no Site |
40
+ | `feedback submit` | Submit sanitized agent feedback. Requires `--command` (1..200 characters), `--comment` (1..2000), `--agent` (1..100), and `--yes`. Optional `--intent bug\|improvement` (default `bug`) and `--request-id` (1..128). Adds the CLI version. Needs no Site. Returns the feedback ID and status |
40
41
  | `login` | Pairs the CLI with a Team through the browser. A person must approve the pairing, so ask the user to run it. `--with-token` reads a token from a hidden prompt or stdin. `--no-browser` skips opening the browser |
41
42
  | `logout` | Discards the stored credential. Returns a `CliLogout` value |
42
43
  | `sites list` | Every accessible Site. The source of Site IDs |