@myna-sh/cli 0.14.0 → 0.16.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/README.md +1 -1
- package/dist/main.js +200 -77
- package/dist/main.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
package/dist/main.js
CHANGED
|
@@ -701,7 +701,7 @@ var ManagementClient = class {
|
|
|
701
701
|
*/
|
|
702
702
|
reports = {
|
|
703
703
|
list: (project, opts = {}) => {
|
|
704
|
-
const { board, status, type, priority, assignee, labels, q, since, ...rest } = opts;
|
|
704
|
+
const { board, status, type, priority, assignee, labels, q, since, reporter, property, ...rest } = opts;
|
|
705
705
|
return this.page(`/projects/${enc(project)}/reports`, rest, {
|
|
706
706
|
board,
|
|
707
707
|
status: Array.isArray(status) ? status.join(",") : status,
|
|
@@ -710,7 +710,9 @@ var ManagementClient = class {
|
|
|
710
710
|
assignee,
|
|
711
711
|
labels: labels?.join(","),
|
|
712
712
|
q,
|
|
713
|
-
since
|
|
713
|
+
since,
|
|
714
|
+
reporter,
|
|
715
|
+
property
|
|
714
716
|
});
|
|
715
717
|
},
|
|
716
718
|
get: (project, report, signal) => this.get(
|
|
@@ -766,13 +768,7 @@ var ManagementClient = class {
|
|
|
766
768
|
void 0,
|
|
767
769
|
signal
|
|
768
770
|
),
|
|
769
|
-
/**
|
|
770
|
-
exposure: (project, report, board, signal) => this.get(
|
|
771
|
-
`/projects/${enc(project)}/reports/${enc(String(report))}/exposure`,
|
|
772
|
-
{ board },
|
|
773
|
-
signal
|
|
774
|
-
),
|
|
775
|
-
/** Move between boards. A public destination needs `confirm: true`. */
|
|
771
|
+
/** Move a report to another board. Ordinary triage; every board is private. */
|
|
776
772
|
move: (project, report, body) => this.mutate(
|
|
777
773
|
"POST",
|
|
778
774
|
`/projects/${enc(project)}/reports/${enc(String(report))}/move`,
|
|
@@ -806,14 +802,58 @@ var ManagementClient = class {
|
|
|
806
802
|
*
|
|
807
803
|
* Unlike every other credential here, one of these is *meant* to be readable
|
|
808
804
|
* by every visitor to a customer's page. What makes that safe lives on the
|
|
809
|
-
* server: the key
|
|
810
|
-
*
|
|
805
|
+
* server: the key can only file a report and read back what the presenting
|
|
806
|
+
* reporter wrote, only from an origin the project registered, and only within
|
|
807
|
+
* its own rate and quota budget.
|
|
808
|
+
*
|
|
809
|
+
* Both `rotate` calls return the new secret and leave the old one working for
|
|
810
|
+
* 24 hours, so a frontend can be rebuilt and redeployed in between.
|
|
811
811
|
*/
|
|
812
812
|
ingestKeys = {
|
|
813
813
|
list: (project, signal) => this.get(`/projects/${enc(project)}/ingest-keys`, void 0, signal),
|
|
814
814
|
create: (project, body) => this.mutate("POST", `/projects/${enc(project)}/ingest-keys`, body),
|
|
815
|
+
rotate: (project, key) => this.mutate(
|
|
816
|
+
"POST",
|
|
817
|
+
`/projects/${enc(project)}/ingest-keys/${enc(key)}/rotate`
|
|
818
|
+
),
|
|
819
|
+
rotateIdentitySecret: (project, key) => this.mutate(
|
|
820
|
+
"POST",
|
|
821
|
+
`/projects/${enc(project)}/ingest-keys/${enc(key)}/rotate-identity-secret`
|
|
822
|
+
),
|
|
815
823
|
revoke: (project, key) => this.mutate("DELETE", `/projects/${enc(project)}/ingest-keys/${enc(key)}`)
|
|
816
824
|
};
|
|
825
|
+
// --- Feedback: reporters --------------------------------------------------
|
|
826
|
+
/**
|
|
827
|
+
* The people who filed the reports.
|
|
828
|
+
*
|
|
829
|
+
* `verified` is the only field Myna stands behind: a signature the project's
|
|
830
|
+
* own backend produced. `properties` is what that backend said about its own
|
|
831
|
+
* user — true of their system, not checked here.
|
|
832
|
+
*/
|
|
833
|
+
reporters = {
|
|
834
|
+
list: (project, opts = {}) => {
|
|
835
|
+
const { q, blocked, ...rest } = opts;
|
|
836
|
+
return this.page(`/projects/${enc(project)}/reporters`, rest, {
|
|
837
|
+
q,
|
|
838
|
+
blocked: blocked === void 0 ? void 0 : String(blocked)
|
|
839
|
+
});
|
|
840
|
+
},
|
|
841
|
+
/** By id (`rpr_…`), by the customer's own user id, or by email. */
|
|
842
|
+
get: (project, reporter, signal) => this.get(
|
|
843
|
+
`/projects/${enc(project)}/reporters/${enc(reporter)}`,
|
|
844
|
+
void 0,
|
|
845
|
+
signal
|
|
846
|
+
),
|
|
847
|
+
/**
|
|
848
|
+
* Block or unblock. Blocking stops notifications and closes read-back; it
|
|
849
|
+
* never deletes what they already filed.
|
|
850
|
+
*/
|
|
851
|
+
setBlocked: (project, reporter, body) => this.mutate(
|
|
852
|
+
"PATCH",
|
|
853
|
+
`/projects/${enc(project)}/reporters/${enc(reporter)}`,
|
|
854
|
+
body
|
|
855
|
+
)
|
|
856
|
+
};
|
|
817
857
|
// --- Declared external checks ---------------------------------------------
|
|
818
858
|
checks = {
|
|
819
859
|
list: (project, signal) => this.get(`/projects/${enc(project)}/checks`, void 0, signal),
|
|
@@ -3141,7 +3181,7 @@ function registerFeedback(program) {
|
|
|
3141
3181
|
);
|
|
3142
3182
|
})
|
|
3143
3183
|
);
|
|
3144
|
-
boards.command("create").description("Create a board").requiredOption("--key <key>", "lowercase key, e.g. qa").requiredOption("--name <name>", "display name").option("--type <type>", "bug | qa | feedback | feature_request", "bug").option("--description <text>", "what this board is for").option("--guidance <text>", "how reports here should be triaged and answered").action(
|
|
3184
|
+
boards.command("create").description("Create a board").requiredOption("--key <key>", "lowercase key, e.g. qa").requiredOption("--name <name>", "display name").option("--type <type>", "bug | qa | feedback | feature_request", "bug").option("--description <text>", "what this board is for").option("--guidance <text>", "how reports here should be triaged and answered").option("--app-url <url>", "where your product shows these reports; linked from notification emails").option("--require-identity", "refuse submissions that carry no server-signed identity").action(
|
|
3145
3185
|
handle(async (ctx, _args, opts) => {
|
|
3146
3186
|
const project = ctx.requireProject();
|
|
3147
3187
|
const board = await ctx.management().boards.create(project, {
|
|
@@ -3149,30 +3189,31 @@ function registerFeedback(program) {
|
|
|
3149
3189
|
name: opts.name,
|
|
3150
3190
|
type: opts.type,
|
|
3151
3191
|
description: opts.description,
|
|
3152
|
-
guidance: opts.guidance
|
|
3192
|
+
guidance: opts.guidance,
|
|
3193
|
+
appUrl: opts.appUrl,
|
|
3194
|
+
requireIdentity: opts.requireIdentity
|
|
3153
3195
|
});
|
|
3154
3196
|
emit(board, () => diag(`Created board ${board.key} (${board.id}).`));
|
|
3155
3197
|
})
|
|
3156
3198
|
);
|
|
3157
|
-
boards.command("update").description("Change a board's name,
|
|
3199
|
+
boards.command("update").description("Change a board's name, app URL, or identity requirement").argument("<board>", "board key or id").option("--name <name>", "display name").option("--description <text>", "what this board is for").option("--guidance <text>", "how reports here should be triaged and answered").option("--app-url <url>", "where your product shows these reports; 'none' to clear it").option("--require-identity", "refuse submissions that carry no server-signed identity").option("--no-require-identity", "accept unsigned submissions").action(
|
|
3158
3200
|
handle(async (ctx, args, opts) => {
|
|
3159
3201
|
const project = ctx.requireProject();
|
|
3160
|
-
const
|
|
3161
|
-
if (visibility && visibility !== "private" && visibility !== "public") {
|
|
3162
|
-
throw new Error(`--visibility must be private or public (got "${visibility}").`);
|
|
3163
|
-
}
|
|
3202
|
+
const appUrl = opts.appUrl;
|
|
3164
3203
|
const board = await ctx.management().boards.update(project, args[0], {
|
|
3165
3204
|
name: opts.name,
|
|
3166
3205
|
description: opts.description,
|
|
3167
3206
|
guidance: opts.guidance,
|
|
3168
|
-
|
|
3169
|
-
|
|
3207
|
+
// `none` clears it. An empty string would fail URL validation, and
|
|
3208
|
+
// omitting the flag has to keep meaning "leave it alone".
|
|
3209
|
+
appUrl: appUrl === void 0 ? void 0 : appUrl === "none" ? null : appUrl,
|
|
3210
|
+
requireIdentity: opts.requireIdentity
|
|
3170
3211
|
});
|
|
3171
3212
|
emit(board, () => {
|
|
3172
3213
|
diag(`Updated board ${board.key}.`);
|
|
3173
|
-
if (
|
|
3214
|
+
if (board.requireIdentity && !board.appUrl) {
|
|
3174
3215
|
diag(
|
|
3175
|
-
`
|
|
3216
|
+
`This board requires a signed identity but names no --app-url, so notification emails carry the update with no link back.`
|
|
3176
3217
|
);
|
|
3177
3218
|
}
|
|
3178
3219
|
});
|
|
@@ -3186,7 +3227,7 @@ function registerFeedback(program) {
|
|
|
3186
3227
|
})
|
|
3187
3228
|
);
|
|
3188
3229
|
const reports = feedback.command("reports").description("Read and act on reports");
|
|
3189
|
-
reports.command("list").description("List reports, most recently active first").option("--board <key>", "only this board").option("--status <list>", "comma-separated statuses").option("--type <type>", "bug | qa | feedback | feature_request").option("--priority <priority>", "low | normal | high | urgent").option("--assignee <userId>", "only reports assigned to this user").option("--labels <list>", "comma-separated labels; matches any").option("-q, --query <text>", "full-text search over title and body").option("--since <iso>", "only reports active at or after this time").option("--limit <n>", "page size", "25").action(
|
|
3230
|
+
reports.command("list").description("List reports, most recently active first").option("--board <key>", "only this board").option("--status <list>", "comma-separated statuses").option("--type <type>", "bug | qa | feedback | feature_request").option("--priority <priority>", "low | normal | high | urgent").option("--assignee <userId>", "only reports assigned to this user").option("--labels <list>", "comma-separated labels; matches any").option("-q, --query <text>", "full-text search over title and body").option("--since <iso>", "only reports active at or after this time").option("--reporter <ref>", "a reporter's external id or email address").option("--property <key:value>", "match a reporter property, e.g. plan:enterprise").option("--limit <n>", "page size", "25").action(
|
|
3190
3231
|
handle(async (ctx, _args, opts) => {
|
|
3191
3232
|
const project = ctx.requireProject();
|
|
3192
3233
|
const page = await ctx.management().reports.list(project, {
|
|
@@ -3198,6 +3239,8 @@ function registerFeedback(program) {
|
|
|
3198
3239
|
labels: opts.labels?.split(","),
|
|
3199
3240
|
q: opts.query,
|
|
3200
3241
|
since: opts.since,
|
|
3242
|
+
reporter: opts.reporter,
|
|
3243
|
+
property: opts.property,
|
|
3201
3244
|
limit: Number(opts.limit)
|
|
3202
3245
|
});
|
|
3203
3246
|
emit(
|
|
@@ -3359,50 +3402,13 @@ Timeline:`);
|
|
|
3359
3402
|
);
|
|
3360
3403
|
})
|
|
3361
3404
|
);
|
|
3362
|
-
reports.command("
|
|
3363
|
-
handle(async (ctx, args, opts) => {
|
|
3364
|
-
const project = ctx.requireProject();
|
|
3365
|
-
const e = await ctx.management().reports.exposure(project, args[0], opts.board);
|
|
3366
|
-
emit(e, () => {
|
|
3367
|
-
diag(`Moving to "${e.board.key}" (${e.board.visibility}) would make this readable by anyone:`);
|
|
3368
|
-
diag(`
|
|
3369
|
-
${e.willBePublic.title}`);
|
|
3370
|
-
if (e.willBePublic.body) diag(`
|
|
3371
|
-
${e.willBePublic.body}`);
|
|
3372
|
-
if (e.willBePublic.comments.length > 0) {
|
|
3373
|
-
diag(`
|
|
3374
|
-
Public replies (${e.willBePublic.comments.length}):`);
|
|
3375
|
-
for (const c of e.willBePublic.comments) diag(` ${c.author}: ${c.body}`);
|
|
3376
|
-
}
|
|
3377
|
-
if (e.willBePublic.attachments.length > 0) {
|
|
3378
|
-
diag(`
|
|
3379
|
-
Attachments:`);
|
|
3380
|
-
for (const a of e.willBePublic.attachments) diag(` ${a.filename} (${a.kind}, ${a.byteSize} bytes)`);
|
|
3381
|
-
}
|
|
3382
|
-
if (e.willBePublic.context) diag(`
|
|
3383
|
-
Environment: ${JSON.stringify(e.willBePublic.context)}`);
|
|
3384
|
-
diag(`
|
|
3385
|
-
Stays private: ${e.willStayPrivate.internalComments} internal note(s)` + (e.willStayPrivate.reporterEmail ? `, the reporter's email` : ""));
|
|
3386
|
-
for (const w of e.warnings) diag(`
|
|
3387
|
-
! ${w}`);
|
|
3388
|
-
diag(`
|
|
3389
|
-
Publish with: myna feedback reports move ${args[0]} --board ${opts.board} --confirm`);
|
|
3390
|
-
});
|
|
3391
|
-
})
|
|
3392
|
-
);
|
|
3393
|
-
reports.command("move").description("Move a report to another board").argument("<report>", "report id, number, or #number").requiredOption("--board <key>", "destination board").option("--confirm", "required when the destination is public; run `exposure` first").action(
|
|
3405
|
+
reports.command("move").description("Move a report to another board").argument("<report>", "report id, number, or #number").requiredOption("--board <key>", "destination board").action(
|
|
3394
3406
|
handle(async (ctx, args, opts) => {
|
|
3395
3407
|
const project = ctx.requireProject();
|
|
3396
|
-
const
|
|
3397
|
-
board: opts.board
|
|
3398
|
-
confirm: Boolean(opts.confirm)
|
|
3408
|
+
const report = await ctx.management().reports.move(project, args[0], {
|
|
3409
|
+
board: opts.board
|
|
3399
3410
|
});
|
|
3400
|
-
emit(
|
|
3401
|
-
result,
|
|
3402
|
-
() => diag(
|
|
3403
|
-
result.isPublic ? `#${result.number} is now public on ${result.board}.` : `#${result.number} moved to ${result.board}.`
|
|
3404
|
-
)
|
|
3405
|
-
);
|
|
3411
|
+
emit(report, () => diag(`#${report.number} moved to ${report.boardKey}.`));
|
|
3406
3412
|
})
|
|
3407
3413
|
);
|
|
3408
3414
|
reports.command("link").description("Point a report at a commit, pull request, entry, or release").argument("<report>", "report id, number, or #number").requiredOption("--kind <kind>", "commit | pull_request | entry | release | url").requiredOption("--value <value>", "the sha, URL, id, or release number").option("--title <text>", "label for the link").action(
|
|
@@ -3457,15 +3463,18 @@ Publish with: myna feedback reports move ${args[0]} --board ${opts.board} --conf
|
|
|
3457
3463
|
() => table(rows, [
|
|
3458
3464
|
{ header: "ID", value: (k) => k.id },
|
|
3459
3465
|
{ header: "NAME", value: (k) => k.name },
|
|
3460
|
-
{ header: "BOARD", value: (k) => k.boardKey },
|
|
3466
|
+
{ header: "BOARD", value: (k) => k.boardKey ?? "any" },
|
|
3461
3467
|
{ header: "SIGNED ID", value: (k) => k.hasIdentitySecret ? "yes" : "no" },
|
|
3462
3468
|
{ header: "LAST USED", value: (k) => k.lastUsedAt ?? "never" },
|
|
3463
|
-
{
|
|
3469
|
+
{
|
|
3470
|
+
header: "STATE",
|
|
3471
|
+
value: (k) => k.revokedAt ? "revoked" : k.previousSecretExpiresAt ? "rotating" : "live"
|
|
3472
|
+
}
|
|
3464
3473
|
])
|
|
3465
3474
|
);
|
|
3466
3475
|
})
|
|
3467
3476
|
);
|
|
3468
|
-
keys.command("create").description("Create a publishable ingest key").requiredOption("--name <name>", "what this key is for").
|
|
3477
|
+
keys.command("create").description("Create a publishable ingest key").requiredOption("--name <name>", "what this key is for").option("--board <key>", "restrict it to one board; omit to accept any board on the project").option("--signed-identity", "also mint a server-side secret for signed identify").action(
|
|
3469
3478
|
handle(async (ctx, _args, opts) => {
|
|
3470
3479
|
const project = ctx.requireProject();
|
|
3471
3480
|
const created = await ctx.management().ingestKeys.create(project, {
|
|
@@ -3474,13 +3483,18 @@ Publish with: myna feedback reports move ${args[0]} --board ${opts.board} --conf
|
|
|
3474
3483
|
withIdentitySecret: Boolean(opts.signedIdentity)
|
|
3475
3484
|
});
|
|
3476
3485
|
emit(created, () => {
|
|
3477
|
-
diag(
|
|
3486
|
+
diag(
|
|
3487
|
+
created.boardKey ? `Created ingest key ${created.id} for board ${created.boardKey}.` : `Created ingest key ${created.id} for any board on this project.`
|
|
3488
|
+
);
|
|
3478
3489
|
diag(``);
|
|
3479
3490
|
diag(` ${created.key}`);
|
|
3480
3491
|
diag(``);
|
|
3481
3492
|
diag(`This key is publishable \u2014 it is designed to sit in your browser bundle.`);
|
|
3482
|
-
diag(
|
|
3483
|
-
|
|
3493
|
+
diag(
|
|
3494
|
+
created.boardKey ? `It can only file on ${created.boardKey} and read back what the presenting reporter wrote.` : `It can file on any board on this project and read back what the presenting reporter wrote.`
|
|
3495
|
+
);
|
|
3496
|
+
diag(`Requests are refused from any origin the project has not registered.`);
|
|
3497
|
+
diag(`Add yours with: myna projects update --origins`);
|
|
3484
3498
|
if (created.identitySecret) {
|
|
3485
3499
|
diag(``);
|
|
3486
3500
|
diag(`Identity signing secret (shown once \u2014 keep it on your server):`);
|
|
@@ -3488,15 +3502,115 @@ Publish with: myna feedback reports move ${args[0]} --board ${opts.board} --conf
|
|
|
3488
3502
|
diag(` ${created.identitySecret}`);
|
|
3489
3503
|
diag(``);
|
|
3490
3504
|
diag(`Sign a user id with HMAC-SHA256 and pass it as identify.signature.`);
|
|
3505
|
+
diag(`signIdentity in @myna-sh/sdk/feedback/server does it for you.`);
|
|
3491
3506
|
}
|
|
3492
3507
|
});
|
|
3493
3508
|
})
|
|
3494
3509
|
);
|
|
3510
|
+
keys.command("rotate").description("Mint a new secret, leaving the old one live for 24 hours").argument("<key>", "ingest key id").option("--identity-secret", "rotate the identity signing secret instead of the key").action(
|
|
3511
|
+
handle(async (ctx, args, opts) => {
|
|
3512
|
+
const project = ctx.requireProject();
|
|
3513
|
+
const keyId = args[0];
|
|
3514
|
+
if (opts.identitySecret) {
|
|
3515
|
+
const rotated2 = await ctx.management().ingestKeys.rotateIdentitySecret(project, keyId);
|
|
3516
|
+
emit(rotated2, () => {
|
|
3517
|
+
diag(`Rotated the identity secret on ${rotated2.id} (shown once):`);
|
|
3518
|
+
diag(``);
|
|
3519
|
+
diag(` ${rotated2.identitySecret}`);
|
|
3520
|
+
diag(``);
|
|
3521
|
+
diag(`The old secret keeps verifying until ${rotated2.previousIdentitySecretExpiresAt}.`);
|
|
3522
|
+
});
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
const rotated = await ctx.management().ingestKeys.rotate(project, keyId);
|
|
3526
|
+
emit(rotated, () => {
|
|
3527
|
+
diag(`Rotated ${rotated.id} (shown once):`);
|
|
3528
|
+
diag(``);
|
|
3529
|
+
diag(` ${rotated.key}`);
|
|
3530
|
+
diag(``);
|
|
3531
|
+
diag(`The old key keeps working until ${rotated.previousSecretExpiresAt}. Deploy before then.`);
|
|
3532
|
+
});
|
|
3533
|
+
})
|
|
3534
|
+
);
|
|
3495
3535
|
keys.command("revoke").description("Revoke an ingest key").argument("<key>", "ingest key id").action(
|
|
3496
3536
|
handle(async (ctx, args) => {
|
|
3497
3537
|
const project = ctx.requireProject();
|
|
3498
3538
|
const result = await ctx.management().ingestKeys.revoke(project, args[0]);
|
|
3499
|
-
emit(
|
|
3539
|
+
emit(
|
|
3540
|
+
result,
|
|
3541
|
+
() => diag(
|
|
3542
|
+
`Revoked ${args[0]}. Sites using it can no longer submit, including a secret mid-rotation.`
|
|
3543
|
+
)
|
|
3544
|
+
);
|
|
3545
|
+
})
|
|
3546
|
+
);
|
|
3547
|
+
const people = feedback.command("people").description("The reporters who filed the reports");
|
|
3548
|
+
people.command("list").description("List reporters, most recent first").option("-q, --query <text>", "match an email, name, or external id").option("--blocked", "only blocked reporters").option("--limit <n>", "page size", "25").action(
|
|
3549
|
+
handle(async (ctx, _args, opts) => {
|
|
3550
|
+
const project = ctx.requireProject();
|
|
3551
|
+
const page = await ctx.management().reporters.list(project, {
|
|
3552
|
+
q: opts.query,
|
|
3553
|
+
blocked: opts.blocked ? true : void 0,
|
|
3554
|
+
limit: Number(opts.limit)
|
|
3555
|
+
});
|
|
3556
|
+
emit(
|
|
3557
|
+
{ data: page.data, nextCursor: page.nextCursor },
|
|
3558
|
+
() => table(page.data, [
|
|
3559
|
+
{ header: "ID", value: (r) => r.id },
|
|
3560
|
+
{ header: "WHO", value: (r) => r.displayName ?? r.email ?? r.externalId ?? "\u2014" },
|
|
3561
|
+
{ header: "EXTERNAL", value: (r) => r.externalId ?? "\u2014" },
|
|
3562
|
+
{ header: "VERIFIED", value: (r) => r.verified ? "yes" : "no" },
|
|
3563
|
+
{ header: "REPORTS", value: (r) => String(r.reportCount) },
|
|
3564
|
+
{ header: "LAST SEEN", value: (r) => r.lastSeenAt ?? "never" },
|
|
3565
|
+
{ header: "STATE", value: (r) => r.blocked ? "blocked" : "active" }
|
|
3566
|
+
])
|
|
3567
|
+
);
|
|
3568
|
+
})
|
|
3569
|
+
);
|
|
3570
|
+
people.command("show").description("One reporter and everything they have filed").argument("<reporter>", "reporter id, your own user id, or an email address").action(
|
|
3571
|
+
handle(async (ctx, args) => {
|
|
3572
|
+
const project = ctx.requireProject();
|
|
3573
|
+
const r = await ctx.management().reporters.get(project, args[0]);
|
|
3574
|
+
emit(r, () => {
|
|
3575
|
+
diag(`${r.displayName ?? r.email ?? r.externalId ?? r.id}`);
|
|
3576
|
+
diag(
|
|
3577
|
+
`${r.verified ? "verified" : "unverified"} \xB7 ${r.reportCount} report(s)` + (r.blocked ? " \xB7 blocked" : "")
|
|
3578
|
+
);
|
|
3579
|
+
if (r.email) diag(`email: ${r.email}`);
|
|
3580
|
+
if (r.externalId) diag(`your id: ${r.externalId}`);
|
|
3581
|
+
if (r.properties && Object.keys(r.properties).length > 0) {
|
|
3582
|
+
diag(`
|
|
3583
|
+
Properties (from your application):`);
|
|
3584
|
+
for (const [k, v] of Object.entries(r.properties)) diag(` ${k}: ${JSON.stringify(v)}`);
|
|
3585
|
+
}
|
|
3586
|
+
if (r.reports.length > 0) {
|
|
3587
|
+
diag(``);
|
|
3588
|
+
table(r.reports, [
|
|
3589
|
+
{ header: "#", value: (x) => String(x.number) },
|
|
3590
|
+
{ header: "TITLE", value: (x) => x.title },
|
|
3591
|
+
{ header: "BOARD", value: (x) => x.boardKey },
|
|
3592
|
+
{ header: "STATUS", value: (x) => x.status },
|
|
3593
|
+
{ header: "ACTIVITY", value: (x) => x.lastActivityAt }
|
|
3594
|
+
]);
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
})
|
|
3598
|
+
);
|
|
3599
|
+
people.command("block").description("Stop a reporter filing, reading back, or being notified").argument("<reporter>", "reporter id, your own user id, or an email address").action(
|
|
3600
|
+
handle(async (ctx, args) => {
|
|
3601
|
+
const project = ctx.requireProject();
|
|
3602
|
+
const r = await ctx.management().reporters.setBlocked(project, args[0], { blocked: true });
|
|
3603
|
+
emit(
|
|
3604
|
+
r,
|
|
3605
|
+
() => diag(`Blocked ${r.displayName ?? r.email ?? r.id}. What they already filed is untouched.`)
|
|
3606
|
+
);
|
|
3607
|
+
})
|
|
3608
|
+
);
|
|
3609
|
+
people.command("unblock").description("Let a blocked reporter back in").argument("<reporter>", "reporter id, your own user id, or an email address").action(
|
|
3610
|
+
handle(async (ctx, args) => {
|
|
3611
|
+
const project = ctx.requireProject();
|
|
3612
|
+
const r = await ctx.management().reporters.setBlocked(project, args[0], { blocked: false });
|
|
3613
|
+
emit(r, () => diag(`Unblocked ${r.displayName ?? r.email ?? r.id}.`));
|
|
3500
3614
|
})
|
|
3501
3615
|
);
|
|
3502
3616
|
const attachments = feedback.command("attachments").description("Evidence attached to a report");
|
|
@@ -3683,9 +3797,7 @@ var ID_PREFIXES = {
|
|
|
3683
3797
|
reportAttachment: "rat",
|
|
3684
3798
|
reportAttachmentUpload: "rau",
|
|
3685
3799
|
reportLink: "rlk",
|
|
3686
|
-
ingestKey: "ing"
|
|
3687
|
-
reporterToken: "rtk",
|
|
3688
|
-
reportVote: "rvt"
|
|
3800
|
+
ingestKey: "ing"
|
|
3689
3801
|
};
|
|
3690
3802
|
var PREFIX_SET = new Set(Object.values(ID_PREFIXES));
|
|
3691
3803
|
|
|
@@ -3782,7 +3894,18 @@ var REPORT_LIMITS = {
|
|
|
3782
3894
|
labelsPerReport: 20,
|
|
3783
3895
|
labelChars: 40,
|
|
3784
3896
|
/** Serialized `context`, which a developer supplies and we do not police. */
|
|
3785
|
-
contextBytes: 16 * 1024
|
|
3897
|
+
contextBytes: 16 * 1024,
|
|
3898
|
+
/**
|
|
3899
|
+
* Serialized reporter `properties`, and how many keys they may have.
|
|
3900
|
+
*
|
|
3901
|
+
* Properties are the customer's own facts about their own user — plan, seat
|
|
3902
|
+
* count, the tenant they belong to — sent with a signed identity so a triager
|
|
3903
|
+
* can tell an enterprise regression from a hobbyist's. Bounded because they
|
|
3904
|
+
* are replaced wholesale on every submission and read on every inbox filter,
|
|
3905
|
+
* not because 33 keys would be wrong to want.
|
|
3906
|
+
*/
|
|
3907
|
+
reporterPropertyBytes: 4 * 1024,
|
|
3908
|
+
reporterPropertyKeys: 32
|
|
3786
3909
|
};
|
|
3787
3910
|
|
|
3788
3911
|
// ../shared/dist/rank.js
|
|
@@ -5452,7 +5575,7 @@ function installCommand(manager, version) {
|
|
|
5452
5575
|
}
|
|
5453
5576
|
|
|
5454
5577
|
// src/version.ts
|
|
5455
|
-
var VERSION = true ? "0.
|
|
5578
|
+
var VERSION = true ? "0.16.0" : "0.0.0-dev";
|
|
5456
5579
|
var IS_RELEASE_BUILD = true;
|
|
5457
5580
|
|
|
5458
5581
|
// src/commands/doctor.ts
|
|
@@ -6007,7 +6130,7 @@ function normalizeGlobals(argv) {
|
|
|
6007
6130
|
}
|
|
6008
6131
|
function buildProgram() {
|
|
6009
6132
|
const program = new Command();
|
|
6010
|
-
program.name("myna").description("Myna \u2014 content
|
|
6133
|
+
program.name("myna").description("Myna \u2014 agent-first content and feedback infrastructure").version(VERSION, "-v, --version").option("--json", "emit a single machine-readable JSON value on stdout").option("--project <ref>", "project id or slug").option("--organization <ref>", "organization id or slug").option("--token <token>", "API token (overrides stored credentials)").option("--api-url <url>", "API base URL").option("--no-interactive", "disable prompts and browser opening").showHelpAfterError();
|
|
6011
6134
|
registerAuth(program);
|
|
6012
6135
|
registerDoctor(program);
|
|
6013
6136
|
registerUpdate(program);
|