@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 (
|
|
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
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
1881
|
-
to: { type: 'string', description: 'Comparison crawl
|
|
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.
|
|
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.
|
|
36
|
-
"@nuxtseo/
|
|
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
|
-
"@
|
|
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.
|
|
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
|
|
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 |
|