@myapihq/cli 2.18.0 → 2.19.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.
- package/dist/commands/feedback-capture.test.d.ts +1 -0
- package/dist/commands/feedback-capture.test.js +49 -0
- package/dist/commands/feedback.d.ts +8 -0
- package/dist/commands/feedback.js +61 -4
- package/dist/errors.js +6 -0
- package/dist/skills/my-feedback-api/SKILL.md +98 -81
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Capture flags send only what the operator named.
|
|
2
|
+
//
|
|
3
|
+
// The rule this file defends: an absent `capture` sub-field means "leave it
|
|
4
|
+
// alone", never "off". ImmoPilot's screens carry tenant names, IBANs and dates
|
|
5
|
+
// of birth; the widget photographs the viewport and reads the text of whatever
|
|
6
|
+
// the reporter points at. If `--no-screenshot` also sent `text: true` because
|
|
7
|
+
// the CLI helpfully filled in the object, it would turn one deliberate
|
|
8
|
+
// restriction into a different, undeclared permission — on exactly those pages.
|
|
9
|
+
//
|
|
10
|
+
// Both fields default to true server-side, so this is also what keeps every
|
|
11
|
+
// widget embedded before capture existed behaving as it always did.
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { _captureFrom } from './feedback.js';
|
|
14
|
+
const f = (o) => o;
|
|
15
|
+
describe('_captureFrom', () => {
|
|
16
|
+
it('is undefined when the operator said nothing', () => {
|
|
17
|
+
// Not `{}` — an empty object would still put `capture` in the PATCH body,
|
|
18
|
+
// and "Nothing to change" would stop being reachable.
|
|
19
|
+
expect(_captureFrom(f({}))).toBeUndefined();
|
|
20
|
+
expect(_captureFrom(f({ routes: '/a' }))).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
it('sends only the field that was named', () => {
|
|
23
|
+
expect(_captureFrom(f({ 'no-screenshot': true }))).toEqual({ screenshot: false });
|
|
24
|
+
expect(_captureFrom(f({ 'no-text': true }))).toEqual({ text: false });
|
|
25
|
+
expect(_captureFrom(f({ screenshot: true }))).toEqual({ screenshot: true });
|
|
26
|
+
expect(_captureFrom(f({ text: true }))).toEqual({ text: true });
|
|
27
|
+
});
|
|
28
|
+
it('never infers the field it was not told about', () => {
|
|
29
|
+
// The whole point. Turning screenshots off must not carry an opinion about
|
|
30
|
+
// text, in either direction.
|
|
31
|
+
const only = _captureFrom(f({ 'no-screenshot': true }));
|
|
32
|
+
expect(only).not.toHaveProperty('text');
|
|
33
|
+
expect(Object.keys(only)).toEqual(['screenshot']);
|
|
34
|
+
});
|
|
35
|
+
it('carries both when both were named', () => {
|
|
36
|
+
expect(_captureFrom(f({ 'no-screenshot': true, 'no-text': true })))
|
|
37
|
+
.toEqual({ screenshot: false, text: false });
|
|
38
|
+
expect(_captureFrom(f({ screenshot: true, 'no-text': true })))
|
|
39
|
+
.toEqual({ screenshot: true, text: false });
|
|
40
|
+
});
|
|
41
|
+
it('lets the negative win when both spellings are passed', () => {
|
|
42
|
+
// `--screenshot --no-screenshot` is contradictory input. Resolving it to
|
|
43
|
+
// the restrictive answer is the safe direction: the cost of wrongly not
|
|
44
|
+
// capturing is a less useful bug report, the cost of wrongly capturing is
|
|
45
|
+
// a customer's records in a screenshot.
|
|
46
|
+
expect(_captureFrom(f({ screenshot: true, 'no-screenshot': true }))).toEqual({ screenshot: false });
|
|
47
|
+
expect(_captureFrom(f({ text: true, 'no-text': true }))).toEqual({ text: false });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import { feedback as sdkFeedback } from '@myapihq/sdk';
|
|
1
2
|
import type { FlagSchema } from '../flags.js';
|
|
2
3
|
import { type Flags } from '../helpers.js';
|
|
3
4
|
import type { Exposes } from '../exposes.js';
|
|
4
5
|
export declare const EXPOSES: Exposes;
|
|
5
6
|
export declare const SCHEMA: FlagSchema;
|
|
7
|
+
/** Reads the paired --x / --no-x flags into a capture patch.
|
|
8
|
+
*
|
|
9
|
+
* Only the fields the operator actually named end up in the object. Sending a
|
|
10
|
+
* full `capture` would turn "stop taking screenshots" into "stop taking
|
|
11
|
+
* screenshots and start capturing text", silently changing a setting they did
|
|
12
|
+
* not mention — on a widget that may be pointed at tenant records. */
|
|
13
|
+
export declare function _captureFrom(flags: Flags): sdkFeedback.WidgetCapture | undefined;
|
|
6
14
|
export declare function list(flags: Flags): Promise<void>;
|
|
7
15
|
export declare function create(bodyArg: string | undefined, flags: Flags): Promise<void>;
|
|
8
16
|
export declare function resolve(id: string, flags: Flags): Promise<void>;
|
|
@@ -36,7 +36,41 @@ export const SCHEMA = {
|
|
|
36
36
|
'page-url': 'string',
|
|
37
37
|
route: 'string',
|
|
38
38
|
origins: 'string',
|
|
39
|
+
// Capture controls. Paired --x / --no-x rather than --x=true|false so that
|
|
40
|
+
// "unset" stays distinguishable from "set to false": the API treats an absent
|
|
41
|
+
// key as unchanged, and the CLI must be able to express that.
|
|
42
|
+
screenshot: 'boolean',
|
|
43
|
+
'no-screenshot': 'boolean',
|
|
44
|
+
text: 'boolean',
|
|
45
|
+
'no-text': 'boolean',
|
|
39
46
|
};
|
|
47
|
+
/** Reads the paired --x / --no-x flags into a capture patch.
|
|
48
|
+
*
|
|
49
|
+
* Only the fields the operator actually named end up in the object. Sending a
|
|
50
|
+
* full `capture` would turn "stop taking screenshots" into "stop taking
|
|
51
|
+
* screenshots and start capturing text", silently changing a setting they did
|
|
52
|
+
* not mention — on a widget that may be pointed at tenant records. */
|
|
53
|
+
export function _captureFrom(flags) {
|
|
54
|
+
const cap = {};
|
|
55
|
+
if (flags['no-screenshot'])
|
|
56
|
+
cap.screenshot = false;
|
|
57
|
+
else if (flags.screenshot)
|
|
58
|
+
cap.screenshot = true;
|
|
59
|
+
if (flags['no-text'])
|
|
60
|
+
cap.text = false;
|
|
61
|
+
else if (flags.text)
|
|
62
|
+
cap.text = true;
|
|
63
|
+
return Object.keys(cap).length ? cap : undefined;
|
|
64
|
+
}
|
|
65
|
+
/** "on" / "off" for a resolved capture field. The server returns both fields
|
|
66
|
+
* resolved, so an undefined here means an older backend that did not answer —
|
|
67
|
+
* say so rather than printing "off" for something that is on. */
|
|
68
|
+
function captureLine(c) {
|
|
69
|
+
if (!c)
|
|
70
|
+
return '(not reported by this backend)';
|
|
71
|
+
const s = (v) => v === undefined ? '?' : v ? 'on' : 'off';
|
|
72
|
+
return `screenshot ${s(c.screenshot)}, text ${s(c.text)}`;
|
|
73
|
+
}
|
|
40
74
|
// The platform's enum, checked against the schema rather than invented.
|
|
41
75
|
const KINDS = ['bug', 'issue', 'suggestion'];
|
|
42
76
|
// A selector says `div#install > button.save`. The context says the button read
|
|
@@ -96,6 +130,15 @@ export async function list(flags) {
|
|
|
96
130
|
const pointing = describeTarget(i);
|
|
97
131
|
if (pointing)
|
|
98
132
|
info(` pointing at ${pointing}`);
|
|
133
|
+
// Whoever the host app named via window.__myapiFeedback.identify(). Opaque
|
|
134
|
+
// to us — their id for their user, never resolved against anything.
|
|
135
|
+
//
|
|
136
|
+
// Printed only when set: an anonymous visitor is still the normal case, and
|
|
137
|
+
// a blank `from` reads as a failure to capture rather than nobody to name.
|
|
138
|
+
// Every item filed before 2026-08-19 carries '' — the column existed and
|
|
139
|
+
// nothing ever wrote it — so most history will show no line here.
|
|
140
|
+
if (i.end_user_ref)
|
|
141
|
+
info(` from ${i.end_user_ref}`);
|
|
99
142
|
const trace = Array.isArray(i.trace) ? i.trace : [];
|
|
100
143
|
if (trace.length) {
|
|
101
144
|
// Errors and failed requests are the highest-signal part — for most
|
|
@@ -244,7 +287,7 @@ export async function widget(sub, arg, flags) {
|
|
|
244
287
|
return;
|
|
245
288
|
}
|
|
246
289
|
if (sub === 'update') {
|
|
247
|
-
requireArg(arg, 'id', 'myapi feedback widget update <id> [--routes /a,/b] [--origins a.com] [--accent #2563eb] [--position bottom-right] [--label "Feedback"]');
|
|
290
|
+
requireArg(arg, 'id', 'myapi feedback widget update <id> [--routes /a,/b] [--origins a.com] [--accent #2563eb] [--position bottom-right] [--label "Feedback"] [--no-screenshot] [--no-text]');
|
|
248
291
|
const patch = {};
|
|
249
292
|
const csv = (v) => typeof v === 'string' ? v.split(',').map(s => s.trim()).filter(Boolean) : undefined;
|
|
250
293
|
if (flags.routes !== undefined)
|
|
@@ -282,7 +325,7 @@ export async function widget(sub, arg, flags) {
|
|
|
282
325
|
info('Feedback already collected through it is kept.');
|
|
283
326
|
return;
|
|
284
327
|
}
|
|
285
|
-
error('Usage: myapi feedback widget create <name> [--origins <list>]\n myapi feedback widget list\n myapi feedback widget update <id> [--routes <list>] [--origins <list>]\n myapi feedback widget revoke <id>');
|
|
328
|
+
error('Usage: myapi feedback widget create <name> [--origins <list>] [--no-screenshot] [--no-text]\n myapi feedback widget list\n myapi feedback widget update <id> [--routes <list>] [--origins <list>] [--no-screenshot] [--no-text]\n myapi feedback widget revoke <id>');
|
|
286
329
|
}
|
|
287
330
|
export const SUBCOMMAND_USAGE = {
|
|
288
331
|
'delete': `myapi feedback delete <id> [--yes] [--org <id>]
|
|
@@ -300,10 +343,24 @@ Newest first. \`total\` is the number of matches, not the page size.`,
|
|
|
300
343
|
'resolve': 'myapi feedback resolve <id> [--org <id>]',
|
|
301
344
|
'widget': `myapi feedback widget list
|
|
302
345
|
myapi feedback widget create <name> [--origins a.com,b.com] [--org <id>]
|
|
346
|
+
myapi feedback widget update <id> [--routes <list>] [--no-screenshot] [--no-text] [--org <id>]
|
|
303
347
|
myapi feedback widget revoke <id> [--yes] [--org <id>]
|
|
304
348
|
|
|
305
349
|
The key a widget mints is PUBLIC — it ships in page source and authenticates
|
|
306
|
-
nobody. --origins stops another site posting through it
|
|
350
|
+
nobody. --origins stops another site posting through it.
|
|
351
|
+
|
|
352
|
+
What a report may collect (both on by default):
|
|
353
|
+
--no-screenshot Stop photographing the viewport.
|
|
354
|
+
--no-text Keep the SHAPE of the element pointed at — tag, role, id,
|
|
355
|
+
data-feedback-id — and drop the words inside it. On a table
|
|
356
|
+
row that is "row, id row-8" instead of the customer's name.
|
|
357
|
+
Pass --screenshot / --text to turn one back on. Flags you omit are left alone,
|
|
358
|
+
so turning one off never silently changes the other. Changes reach live
|
|
359
|
+
browsers within ~5 minutes, with no redeploy of your site.
|
|
360
|
+
|
|
361
|
+
To exempt one panel rather than the whole widget, mark it data-feedback-ignore
|
|
362
|
+
in your page: it is skipped in the element context AND blacked out in the
|
|
363
|
+
screenshot.`,
|
|
307
364
|
};
|
|
308
365
|
export async function run(subcommand, args, flags) {
|
|
309
366
|
if (!subcommand || (flags.help && !subcommand)) {
|
|
@@ -320,7 +377,7 @@ Subcommands:
|
|
|
320
377
|
widget create <name> Mint a PUBLIC widget key for a site (--origins to restrict)
|
|
321
378
|
widget list List widgets with the script tag to paste
|
|
322
379
|
widget revoke <id> Revoke a widget key; collected feedback is kept
|
|
323
|
-
widget update <id> Change routes/origins/theme, keeping the same key
|
|
380
|
+
widget update <id> Change routes/origins/theme/capture, keeping the same key
|
|
324
381
|
|
|
325
382
|
All commands accept --org <id> (or set default: myapi config set-org <id>).`);
|
|
326
383
|
return;
|
package/dist/errors.js
CHANGED
|
@@ -26,6 +26,12 @@ export const ERROR_MESSAGES = {
|
|
|
26
26
|
invalid_org: 'Invalid organization.',
|
|
27
27
|
FORBIDDEN: 'You do not have permission to perform this action.',
|
|
28
28
|
RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
|
|
29
|
+
// The backend refuses an unknown key or a non-boolean rather than ignoring it
|
|
30
|
+
// — `{"screenshots": false}` (plural) is a 422, not a no-op. That is
|
|
31
|
+
// deliberate: a capture setting accepted and ignored reads as "screenshots
|
|
32
|
+
// are off" while screenshots keep being taken, on pages that may carry
|
|
33
|
+
// customer records.
|
|
34
|
+
INVALID_CAPTURE: 'capture accepts only `screenshot` and `text`, both booleans. Check the spelling — an unknown key is refused, not ignored.',
|
|
29
35
|
BODY_TOO_LARGE: 'The request is over 64 KB — usually an oversized feedback `trace`. Nothing was saved. This used to surface as INVALID_JSON, which sent people looking for a syntax error they did not have.',
|
|
30
36
|
INSUFFICIENT_BALANCE: 'Insufficient balance. Run: myapi billing topup <amount>',
|
|
31
37
|
INVALID_AMOUNT: 'Amount out of range. Maximum single top-up is $100. Run: myapi billing topup <amount>',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-feedback-api
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.6.0
|
|
4
4
|
description: >
|
|
5
5
|
Collect feedback from the people using what you built. A public widget key lets a page submit without a credential; you list, filter and resolve the results. Kind is chosen by the person reporting, not inferred from their wording.
|
|
6
6
|
triggers: [feedback, bug report, user feedback, feature request, widget, support, complaints, praise]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-f8be1cee17e2dc5e1dcf2310cf18d275982d54460af26a0b642fc46bd8c4691e
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyFeedbackAPI
|
|
@@ -18,111 +18,129 @@ Two halves: a **widget key** a page embeds, and the **items** it produces.
|
|
|
18
18
|
|
|
19
19
|
### The widget key is public, and that is the point
|
|
20
20
|
|
|
21
|
-
`
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
`widget create <name>` mints a key that ships in your page source. It is **not a
|
|
22
|
+
secret** — it names your org so a visitor can submit without signing in, and
|
|
23
|
+
authenticates nobody.
|
|
24
24
|
|
|
25
|
-
Treating it as a credential is the mistake
|
|
26
|
-
|
|
27
|
-
cannot post through it:
|
|
25
|
+
Treating it as a credential is the mistake: people hide it and the widget cannot
|
|
26
|
+
work. It needs `--origins`, so another site cannot post through it:
|
|
28
27
|
|
|
29
28
|
```bash
|
|
30
29
|
myapi feedback widget create site --origins app.example.com,example.com
|
|
31
30
|
```
|
|
32
31
|
|
|
33
|
-
Revoke with `myapi feedback widget revoke <id
|
|
34
|
-
feedback
|
|
32
|
+
Revoke with `myapi feedback widget revoke <id>`: submissions stop immediately,
|
|
33
|
+
collected feedback is kept.
|
|
35
34
|
|
|
36
35
|
### Embedding it: one script tag
|
|
37
36
|
|
|
38
|
-
The platform hosts the widget, compiled per key with
|
|
39
|
-
|
|
37
|
+
The platform hosts the widget, compiled per key with its routes and theme baked
|
|
38
|
+
in. The intended way in — you build no UI:
|
|
40
39
|
|
|
41
40
|
```html
|
|
42
41
|
<script src="https://api.myapihq.com/feedback/in/<widget_key>/widget.js" async></script>
|
|
43
42
|
```
|
|
44
43
|
|
|
45
|
-
It
|
|
46
|
-
|
|
47
|
-
the change reaches browsers within ~5 minutes with no redeploy of your site.
|
|
44
|
+
It shows only on the widget's routes — set them with `widget update <id>
|
|
45
|
+
--routes /app,/app/**`; live in ~5 min, no redeploy.
|
|
48
46
|
|
|
49
|
-
The person points at an element or drags a region
|
|
47
|
+
The person points at an element or drags a region; it sends:
|
|
50
48
|
|
|
51
49
|
- `kind`, `body`, `page_url`, `route`, `viewport`
|
|
52
|
-
- `target_selector` and `target_region` `{x,y,w,h}` —
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- `target_context` — what the element *was*: its text, role, the heading above it
|
|
50
|
+
- `target_selector` and `target_region` `{x,y,w,h}` — on **every** report: the
|
|
51
|
+
element's bounding box for a click, the dragged box for a drag
|
|
52
|
+
- `target_context` — what the element *was*: text, role, the heading above it
|
|
56
53
|
- `trace` — the last events before the report
|
|
57
54
|
|
|
58
|
-
A wrong key
|
|
59
|
-
|
|
60
|
-
in your app.
|
|
55
|
+
A wrong key cannot break the page: the response is `application/javascript`
|
|
56
|
+
with a comment naming the fix, so a typo throws no syntax error.
|
|
61
57
|
|
|
62
|
-
**Screenshots are automatic — you
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
deliberate: an image that fails to render or upload never costs the report.
|
|
58
|
+
**Screenshots are automatic — you build no capture step.** The widget attaches
|
|
59
|
+
the picture as a second call after the report is saved, so an image that fails
|
|
60
|
+
to render never costs the report.
|
|
66
61
|
|
|
67
|
-
The image is **
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
JPEG or WebP up to 3 MB; over that the feedback is kept without the picture.
|
|
62
|
+
The image is **private in your org's assets**; listing returns a **signed link
|
|
63
|
+
good for ~an hour** (`screenshot_url`) — re-list rather than persisting it.
|
|
64
|
+
PNG/JPEG/WebP up to 3 MB; over that the report is kept without it.
|
|
71
65
|
|
|
72
66
|
### Submitting by hand (only if you cannot use the script)
|
|
73
67
|
|
|
74
|
-
`POST https://api.myapihq.com/feedback/in/<widget_key>` — no auth, no SDK
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```js
|
|
78
|
-
await fetch(`https://api.myapihq.com/feedback/in/${WIDGET_KEY}`, {
|
|
79
|
-
method: 'POST',
|
|
80
|
-
headers: { 'Content-Type': 'application/json' },
|
|
81
|
-
body: JSON.stringify({ kind: 'bug', body: text, page_url: location.href }),
|
|
82
|
-
});
|
|
83
|
-
```
|
|
68
|
+
`POST https://api.myapihq.com/feedback/in/<widget_key>` — no auth, no SDK, same
|
|
69
|
+
fields the widget sends (`kind`, `body`, `page_url`, …).
|
|
84
70
|
|
|
85
71
|
Errors: `WIDGET_NOT_FOUND` (a revoked key reads like an invented one, so keys
|
|
86
72
|
cannot be probed), `ORIGIN_NOT_ALLOWED`, `RATE_LIMITED`, `INVALID_KIND`,
|
|
87
|
-
`BODY_REQUIRED`, `BODY_TOO_LONG` (
|
|
88
|
-
`
|
|
89
|
-
`trace`). Nothing is saved in either case. The key only ever writes.
|
|
73
|
+
`BODY_REQUIRED`, `BODY_TOO_LONG` (>8000 chars), `BODY_TOO_LARGE` (>64 KB, usually
|
|
74
|
+
an oversized `trace`). Nothing is saved; the key only writes.
|
|
90
75
|
|
|
91
76
|
### Kind is a claim, not a guess
|
|
92
77
|
|
|
93
|
-
`--kind` is one of `bug`, `issue`, `suggestion`, and it is **what the
|
|
94
|
-
|
|
95
|
-
they believe the product is broken — which is a different and more urgent
|
|
78
|
+
`--kind` is one of `bug`, `issue`, `suggestion`, and it is **what the reporter
|
|
79
|
+
says it is**. A `bug` means they believe the product is broken — a more urgent
|
|
96
80
|
signal than a wish for something new. Do not re-classify from the wording.
|
|
97
81
|
|
|
98
|
-
Classification and duplicate-grouping
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
Classification and duplicate-grouping are designed but not built, so treat
|
|
83
|
+
`kind` as the person's own label, not a processed signal.
|
|
84
|
+
|
|
85
|
+
### Controlling what a report contains
|
|
86
|
+
|
|
87
|
+
A report carries a screenshot of the viewport plus the text of the element
|
|
88
|
+
pointed at. On a page of customer records, that is the page. Both on by default:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
myapi feedback widget update <id> --no-screenshot # stop photographing
|
|
92
|
+
myapi feedback widget update <id> --no-text # keep SHAPE, drop words
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`--no-text` keeps tag, role, id and `data-feedback-id`: on a table row, "row, id
|
|
96
|
+
`row-8`" not the tenant's name. `--screenshot` / `--text` turn one back on.
|
|
97
|
+
Omitted flags are left alone, so one off never changes the other. Live in ~5 min.
|
|
98
|
+
|
|
99
|
+
**In your page**, two levers only you control:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
window.__myapiFeedback.identify("your-user-id"); // when you learn who it is
|
|
103
|
+
window.__myapiFeedback.identify(); // on sign-out
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Callable any time, even long after load. Your id for your user — opaque to us,
|
|
107
|
+
never resolved, posted to a public endpoint: a label, not authentication. Shows
|
|
108
|
+
as `from` in `feedback list`.
|
|
109
|
+
|
|
110
|
+
Mark an element `data-feedback-ignore` to skip it in the context AND black it out
|
|
111
|
+
in the screenshot.
|
|
112
|
+
|
|
113
|
+
| Need | Lever |
|
|
114
|
+
|---|---|
|
|
115
|
+
| No pictures at all | `--no-screenshot` |
|
|
116
|
+
| Pictures, no words | `--no-text` |
|
|
117
|
+
| All but one panel | `data-feedback-ignore` |
|
|
118
|
+
| Different rules per page | two widgets, two route sets |
|
|
119
|
+
|
|
120
|
+
The last row is usually right: capture off on record-bearing routes, on
|
|
121
|
+
elsewhere — not one global switch.
|
|
101
122
|
|
|
102
123
|
### Reading it back
|
|
103
124
|
|
|
104
125
|
Each item carries what was pointed at, how they got there, and a picture.
|
|
105
|
-
`
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
through.
|
|
109
|
-
|
|
110
|
-
Two
|
|
111
|
-
a
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`myapi feedback resolve <id>` closes an item. An unknown id answers the same
|
|
124
|
-
way as an already-resolved one, so a success is not proof the item existed —
|
|
125
|
-
that is deliberate, so ids cannot be probed across orgs.
|
|
126
|
+
`list` summarises it — the element's own text rather than its CSS selector,
|
|
127
|
+
counts of steps/errors/failed requests, whether a screenshot exists, and `from`
|
|
128
|
+
when the page called `identify()`. `--trace` expands the events; `--json` passes
|
|
129
|
+
everything through.
|
|
130
|
+
|
|
131
|
+
Two not to misread: **`trace[].t` is ms since page load**, not clock time — only
|
|
132
|
+
a delta means anything. And **`target_context.filled` says an input had a value,
|
|
133
|
+
never what**.
|
|
134
|
+
|
|
135
|
+
`delete` removes the screenshot with the report — what matters is somebody
|
|
136
|
+
erasing a report because of what is in the picture.
|
|
137
|
+
|
|
138
|
+
`list` is newest first, filterable by `--kind`/`--status`, paged with `--limit`
|
|
139
|
+
/ `--offset`. **`total` is the match count, not the page size**; `has_more` flags
|
|
140
|
+
truncation — both describe the whole match set.
|
|
141
|
+
|
|
142
|
+
`resolve <id>` closes an item. An unknown id answers like an already-resolved
|
|
143
|
+
one, so success is not proof it existed — deliberate, so ids cannot be probed.
|
|
126
144
|
<!-- llm:end -->
|
|
127
145
|
|
|
128
146
|
## Commands
|
|
@@ -135,7 +153,7 @@ that is deliberate, so ids cannot be probed across orgs.
|
|
|
135
153
|
| `myapi feedback delete <id>` | Erase an item — for spam, or personal details typed into the box |
|
|
136
154
|
| `myapi feedback widget create <name> [--origins a.com,b.com]` | Mint a PUBLIC widget key for a site |
|
|
137
155
|
| `myapi feedback widget list` | List widgets, each with the script tag to paste |
|
|
138
|
-
| `myapi feedback widget update <id>` | Change `--routes`, `--origins`, `--name`,
|
|
156
|
+
| `myapi feedback widget update <id>` | Change `--routes`, `--origins`, `--name`, look (`--accent`/`--position`/`--label`) or capture (`--no-screenshot`/`--no-text`); same key |
|
|
139
157
|
| `myapi feedback widget revoke <id>` | Revoke a key; collected feedback is kept |
|
|
140
158
|
<!-- generated:end -->
|
|
141
159
|
|
|
@@ -149,16 +167,16 @@ myapi feedback widget create marketing --origins example.com,www.example.com
|
|
|
149
167
|
# 2. See your widgets and the exact tag to paste
|
|
150
168
|
myapi feedback widget list
|
|
151
169
|
|
|
152
|
-
# 3. Change where it appears
|
|
170
|
+
# 3. Change where it appears, or what it may collect — same key either way
|
|
153
171
|
myapi feedback widget update <id> --routes /app,/app/**
|
|
172
|
+
myapi feedback widget update <id> --no-screenshot --no-text # record-bearing routes
|
|
154
173
|
|
|
155
|
-
# 4. Read what came back, newest first
|
|
174
|
+
# 4. Read what came back, newest first (`from` shows if the page identify()d)
|
|
156
175
|
myapi feedback list --status open
|
|
157
176
|
myapi feedback list --kind bug --limit 20
|
|
158
177
|
|
|
159
178
|
# 5. Record something yourself (support call, your own testing)
|
|
160
|
-
myapi feedback create "checkout 500s on
|
|
161
|
-
--route /checkout
|
|
179
|
+
myapi feedback create "checkout 500s on retry" --kind bug --route /checkout
|
|
162
180
|
|
|
163
181
|
# 6. Close it — or delete it, which also removes its screenshot
|
|
164
182
|
myapi feedback resolve <id>
|
|
@@ -168,12 +186,11 @@ myapi feedback delete <id> --yes
|
|
|
168
186
|
|
|
169
187
|
## Notes
|
|
170
188
|
|
|
171
|
-
- The widget key is public by design — restrict
|
|
172
|
-
- `resolve` is idempotent and deliberately indistinguishable from an unknown id.
|
|
189
|
+
- The widget key is public by design — restrict with `--origins`, don't hide it.
|
|
173
190
|
- Feedback survives widget revocation.
|
|
174
|
-
- **`resolve` keeps, `delete` erases.** A public inbox collects spam and the
|
|
175
|
-
|
|
176
|
-
|
|
191
|
+
- **`resolve` keeps, `delete` erases.** A public inbox collects spam and the odd
|
|
192
|
+
report with personal details in it; `delete` is how those leave. Both are
|
|
193
|
+
idempotent and answer the same for an unknown id, so ids cannot be probed.
|
|
177
194
|
|
|
178
195
|
## HTTP (from deployed code)
|
|
179
196
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.19.0",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@myapihq/sdk": "^2.
|
|
49
|
+
"@myapihq/sdk": "^2.19.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^25.6.0",
|