prreviewbuddy 0.27.2 → 0.30.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/CHANGELOG.md +72 -0
- package/README.md +9 -8
- package/dist/{delete_review-B3xciJ03.js → delete_review-B_2J2Wn9.js} +217 -22
- package/dist/main.js +102 -108
- package/dist/server.js +259 -51
- package/package.json +1 -1
- package/static/reviews.js +1 -1
- package/static/shell.css +72 -1
- package/static/workspace.js +29 -29
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { $t as writeAgentPreference, A as removeWorktree, B as workspaceUrl, C as recordTelemetryUploadConsent, Dt as saveWorkspace, Et as reviewedRepositories, G as DEFAULT_WORKSPACE_PORT, H as BUILD_VERSION, I as ensureServer, J as parseWorkspacePort, Jt as git, K as MAX_WORKSPACE_PORT, Kt as resolveTarget, M as relativeTime, N as readIndexToken, O as MANAGED_ROOT, Ot as summarise, P as bootstrapUrl, Q as forgeResolver, Qt as readAgentPreference, R as reviewsUrl, S as record$1, St as positionInLineage, U as PACKAGE_NAME, X as statedWorkspacePort, Xt as resolveModel, Yt as resolveAgent, Z as writePortPreference, a as wasBlocked, at as isTerminal, b as startJob, bt as loadWorkspace, c as updateReview, ct as PHASES, dt as purposeOf, en as CONFIG_PATH, f as UpdateAlreadyRunningError, ft as clearClaim, g as recordKissRun, ht as groupByLineage, i as liveJobsFor, in as agentById, j as modelHelpLines, k as readMarker, m as runningJobs, mt as followedRefName, n as deleteReview$1, nn as AGENT_IDS, nt as allJobs, on as detectAgents, ot as loadJob, p as liveUpdateFor, pt as isClaimed, q as MIN_WORKSPACE_PORT, qt as displayRef, rn as DEFAULT_AGENT_ID, tn as STORE_ROOT, tt as checkFreshness, ut as progressSteps, v as reanalyseReview, vt as lineageKeyFor, w as telemetryUploadConsent, wt as recentWorkspaces, xt as matchingWorkspaceIds, y as runJob, z as stopServer } from "./delete_review-B_2J2Wn9.js";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -987,9 +987,9 @@ your own checkout is never read or written while it works.
|
|
|
987
987
|
prreviewbuddy config get agent the coding agent used when you do not name one
|
|
988
988
|
prreviewbuddy config set agent <agent> remember ${AGENT_NAMES}
|
|
989
989
|
prreviewbuddy config get telemetry-upload
|
|
990
|
-
whether
|
|
990
|
+
whether usage telemetry is sent. On by default
|
|
991
991
|
prreviewbuddy config set telemetry-upload on|off
|
|
992
|
-
send
|
|
992
|
+
send it, or stop
|
|
993
993
|
prreviewbuddy config get port the port the workspace runs on
|
|
994
994
|
prreviewbuddy config set port <port> move it, from the next start onwards
|
|
995
995
|
prreviewbuddy config path where these settings are kept, to edit by hand
|
|
@@ -1119,48 +1119,46 @@ function unknownSetting(key, known) {
|
|
|
1119
1119
|
return `There is no setting called \`${key}\`. This can read and set: ${known.join(", ")}.`;
|
|
1120
1120
|
}
|
|
1121
1121
|
/**
|
|
1122
|
-
* The
|
|
1122
|
+
* The one-time disclosure, in four lines.
|
|
1123
1123
|
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1124
|
+
* Four because a first run is not the place for a privacy policy, and the policy is one line away.
|
|
1125
|
+
* It names both lists, and the one that is never sent is the longer of the two: printing the longer
|
|
1126
|
+
* one is the honest way round when the thing being disclosed is on without anybody asking.
|
|
1127
|
+
*
|
|
1128
|
+
* "Non-identifying" rather than "anonymous", which this copy said until 0.30.0. The payload carries
|
|
1129
|
+
* a random installation UUID that persists, and a stable identifier makes data pseudonymous however
|
|
1130
|
+
* little else is attached to it. "Anonymous" is a word with a technical meaning strong enough to be
|
|
1131
|
+
* challenged on, and the claim below does not need it to be true.
|
|
1128
1132
|
*/
|
|
1129
|
-
function
|
|
1133
|
+
function telemetryNotice() {
|
|
1130
1134
|
return [
|
|
1131
1135
|
"",
|
|
1132
|
-
"
|
|
1133
|
-
"Never your code, diffs,
|
|
1134
|
-
"
|
|
1136
|
+
"Non-identifying usage telemetry is on by default: review start and finish, version,",
|
|
1137
|
+
"platform, agent, success and duration. Never your code, diffs, repository names or",
|
|
1138
|
+
"review content.",
|
|
1139
|
+
"Turn it off: `prreviewbuddy config set telemetry-upload off`",
|
|
1140
|
+
"Details: https://prreviewbuddy.com/privacy",
|
|
1135
1141
|
""
|
|
1136
1142
|
].join("\n");
|
|
1137
1143
|
}
|
|
1138
|
-
function telemetryUploadUnclear() {
|
|
1139
|
-
return "Please answer y or n.\n";
|
|
1140
|
-
}
|
|
1141
|
-
function telemetryUploadGranted() {
|
|
1142
|
-
return "Thank you. Turn it off whenever you like with `prreviewbuddy config set telemetry-upload off`.";
|
|
1143
|
-
}
|
|
1144
|
-
function telemetryUploadDeclined() {
|
|
1145
|
-
return "Nothing will be sent. Turn it on with `prreviewbuddy config set telemetry-upload on`.";
|
|
1146
|
-
}
|
|
1147
1144
|
/**
|
|
1148
|
-
* What `config get telemetry-upload` says, including for the machine that
|
|
1145
|
+
* What `config get telemetry-upload` says, including for the machine that never chose.
|
|
1149
1146
|
*
|
|
1150
|
-
* Three answers rather than two,
|
|
1151
|
-
*
|
|
1147
|
+
* Three answers rather than two, and the third survives the move to a default because it still
|
|
1148
|
+
* names a different fact: "on because that is the default" and "on because you turned it on" are
|
|
1149
|
+
* the same behaviour arrived at two ways, and only one of them is a decision somebody made. The
|
|
1150
|
+
* same distinction `noAgentPreference` keeps next door.
|
|
1152
1151
|
*/
|
|
1153
1152
|
function telemetryUploadIs(consent) {
|
|
1154
1153
|
if (consent === null) return [
|
|
1155
|
-
"
|
|
1154
|
+
"Non-identifying usage telemetry is on by default, and you have not changed it.",
|
|
1156
1155
|
"",
|
|
1157
|
-
"
|
|
1158
|
-
"Answer it now with `prreviewbuddy config set telemetry-upload on` or `off`."
|
|
1156
|
+
"Turn it off with `prreviewbuddy config set telemetry-upload off`."
|
|
1159
1157
|
].join("\n");
|
|
1160
|
-
return consent ? "
|
|
1158
|
+
return consent ? "Non-identifying usage telemetry is being sent, because you turned it on. Turn it off with `prreviewbuddy config set telemetry-upload off`." : "Usage telemetry is not being sent, because you turned it off.";
|
|
1161
1159
|
}
|
|
1162
1160
|
function telemetryUploadSet(on) {
|
|
1163
|
-
return on ? "
|
|
1161
|
+
return on ? "Non-identifying usage telemetry will be sent. Never your code, diffs, file paths, repository names, prompts or review content." : "Nothing will be sent. The usage log on this machine is unchanged; `PRB_TELEMETRY=off` turns that off too.";
|
|
1164
1162
|
}
|
|
1165
1163
|
/**
|
|
1166
1164
|
* The workspace port as it stands, and how it is changed.
|
|
@@ -2384,7 +2382,7 @@ async function operate(command, workspace) {
|
|
|
2384
2382
|
}
|
|
2385
2383
|
workspace.lastUpdate = outcome;
|
|
2386
2384
|
saveWorkspace(workspace);
|
|
2387
|
-
record({
|
|
2385
|
+
record$1({
|
|
2388
2386
|
event: "review_updated",
|
|
2389
2387
|
reviewId: workspace.id,
|
|
2390
2388
|
ok: outcome.ok,
|
|
@@ -3214,7 +3212,11 @@ async function uninstall(command, deps = REAL) {
|
|
|
3214
3212
|
//#endregion
|
|
3215
3213
|
//#region src/first_run.ts
|
|
3216
3214
|
/**
|
|
3217
|
-
* The
|
|
3215
|
+
* The things somebody is told once on a machine, and never again.
|
|
3216
|
+
*
|
|
3217
|
+
* Two of them now: the two lines of hello, and the disclosure that usage telemetry is on. They are
|
|
3218
|
+
* the same kind of fact and are kept the same way, so they share the marker helper at the bottom
|
|
3219
|
+
* rather than each growing their own copy of it.
|
|
3218
3220
|
*
|
|
3219
3221
|
* This began as a `postinstall` script, which is the obvious place for it and the wrong one: npm 11
|
|
3220
3222
|
* does not run install scripts unless they are allow-listed, so the banner would have reached
|
|
@@ -3231,84 +3233,78 @@ async function uninstall(command, deps = REAL) {
|
|
|
3231
3233
|
* as the telemetry notice next to it. A home directory that cannot be written to costs somebody a
|
|
3232
3234
|
* repeated banner and never a review, which is why nothing here throws.
|
|
3233
3235
|
*/
|
|
3234
|
-
var
|
|
3236
|
+
var GREETING = "onboarding-shown";
|
|
3237
|
+
var TELEMETRY_NOTICE = "telemetry-notice-shown";
|
|
3238
|
+
/** Whether this machine has been greeted. */
|
|
3239
|
+
function greeted() {
|
|
3240
|
+
return shown(GREETING);
|
|
3241
|
+
}
|
|
3242
|
+
function recordGreeting() {
|
|
3243
|
+
record(GREETING);
|
|
3244
|
+
}
|
|
3235
3245
|
/**
|
|
3236
|
-
* Whether this machine has been
|
|
3246
|
+
* Whether this machine has been told that usage telemetry is on.
|
|
3237
3247
|
*
|
|
3238
|
-
*
|
|
3239
|
-
*
|
|
3248
|
+
* A second marker rather than a reuse of the greeting: every machine installed before 0.30.0 was
|
|
3249
|
+
* greeted long ago and has still never been told about the default, so reading one from the other
|
|
3250
|
+
* would silence the disclosure for exactly the population it exists for.
|
|
3240
3251
|
*/
|
|
3241
|
-
function
|
|
3252
|
+
function telemetryNoticed() {
|
|
3253
|
+
return shown(TELEMETRY_NOTICE);
|
|
3254
|
+
}
|
|
3255
|
+
function recordTelemetryNotice() {
|
|
3256
|
+
record(TELEMETRY_NOTICE);
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Unreadable reads as already shown.
|
|
3260
|
+
*
|
|
3261
|
+
* Failing closed on repetition: the cost of this being wrong is a disclosure or a hello that never
|
|
3262
|
+
* arrives on a machine whose home cannot be read, and the cost of the other direction is one of
|
|
3263
|
+
* them printed over somebody's output every single time they run a command.
|
|
3264
|
+
*/
|
|
3265
|
+
function shown(name) {
|
|
3242
3266
|
try {
|
|
3243
|
-
return existsSync(
|
|
3267
|
+
return existsSync(join(STORE_ROOT, name));
|
|
3244
3268
|
} catch {
|
|
3245
3269
|
return true;
|
|
3246
3270
|
}
|
|
3247
3271
|
}
|
|
3248
|
-
function
|
|
3272
|
+
function record(name) {
|
|
3249
3273
|
try {
|
|
3250
3274
|
mkdirSync(STORE_ROOT, { recursive: true });
|
|
3251
|
-
writeFileSync(
|
|
3275
|
+
writeFileSync(join(STORE_ROOT, name), `${(/* @__PURE__ */ new Date()).toISOString()}\n`, { mode: 384 });
|
|
3252
3276
|
} catch {}
|
|
3253
3277
|
}
|
|
3254
3278
|
//#endregion
|
|
3255
|
-
//#region src/
|
|
3256
|
-
/**
|
|
3257
|
-
* The one question this product asks about itself, asked once per machine.
|
|
3258
|
-
*
|
|
3259
|
-
* `packages/review-harness/src/telemetry/` has recorded usage events to a local file since it
|
|
3260
|
-
* existed, disclosed and on by default. Sending a subset of them to a server we run is a different
|
|
3261
|
-
* act, and `apps/website/src/pages/Privacy.tsx` promised in as many words that if it ever happened
|
|
3262
|
-
* it would be opt in and off by default. This is how that promise is kept.
|
|
3263
|
-
*
|
|
3264
|
-
* Hand-rolled on `node:readline` beside `select.ts` and `confirm.ts`, for the reason they both
|
|
3265
|
-
* give: this binary has no runtime dependencies and one question is not worth acquiring one. The io
|
|
3266
|
-
* is injected for the same reason theirs is, so what the answer means is testable without a shell.
|
|
3267
|
-
*
|
|
3268
|
-
* **A bare Return re-asks rather than declining.** `confirm.ts` next door defaults to no on Return,
|
|
3269
|
-
* which is right there because every one of its callers is about to delete something. Here it would
|
|
3270
|
-
* be wrong in a subtler way: "no default" was the decision, and a capitalised `N` in the brackets
|
|
3271
|
-
* turns it into opt-out-by-keystroke, which is a different thing wearing the same words. Somebody
|
|
3272
|
-
* who has not read the question has not answered it.
|
|
3273
|
-
*
|
|
3274
|
-
* **No terminal means no question and no answer.** A durable job, a CI run and a piped invocation
|
|
3275
|
-
* must not block on a question nobody can see, and must not be recorded as having declined one:
|
|
3276
|
-
* the setting stays unset, nothing is sent, and the same machine is asked properly the first time
|
|
3277
|
-
* a person runs this interactively.
|
|
3278
|
-
*/
|
|
3279
|
-
async function askTelemetryUpload(options) {
|
|
3280
|
-
const stored = telemetryUploadConsent();
|
|
3281
|
-
if (stored !== null) return { kind: stored ? "granted" : "declined" };
|
|
3282
|
-
if (!options.isTTY) return { kind: "unanswered" };
|
|
3283
|
-
const reader = createInterface({ input: options.input });
|
|
3284
|
-
const lines = reader[Symbol.asyncIterator]();
|
|
3285
|
-
try {
|
|
3286
|
-
options.write(`${telemetryUploadQuestion()}\n`);
|
|
3287
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
3288
|
-
options.write("[y/n] ");
|
|
3289
|
-
const next = await lines.next();
|
|
3290
|
-
if (next.done) return { kind: "unanswered" };
|
|
3291
|
-
const answer = next.value.trim().toLowerCase();
|
|
3292
|
-
if (answer === "y" || answer === "yes") return settle(options, true);
|
|
3293
|
-
if (answer === "n" || answer === "no") return settle(options, false);
|
|
3294
|
-
options.write(telemetryUploadUnclear());
|
|
3295
|
-
}
|
|
3296
|
-
return { kind: "unanswered" };
|
|
3297
|
-
} finally {
|
|
3298
|
-
reader.close();
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3279
|
+
//#region src/telemetry_notice.ts
|
|
3301
3280
|
/**
|
|
3302
|
-
*
|
|
3281
|
+
* What this product says about itself, said once per machine.
|
|
3282
|
+
*
|
|
3283
|
+
* Until 0.30.0 this file's predecessor asked a question: nothing was transmitted until somebody
|
|
3284
|
+
* typed y, and `apps/website/src/pages/Privacy.tsx` promised in as many words that it would work
|
|
3285
|
+
* that way. The sample that produced was not a sample of users, it was a sample of people who
|
|
3286
|
+
* answer questions, and one or two installations reporting against hundreds of weekly downloads
|
|
3287
|
+
* answers none of the four questions the telemetry exists for. So the default changed, the promise
|
|
3288
|
+
* was rewritten rather than quietly edited, and the question became this.
|
|
3289
|
+
*
|
|
3290
|
+
* **A disclosure, not a question wearing different words.** No prompt, no keystroke, no default in
|
|
3291
|
+
* brackets. Asking anything at all, however phrased, goes back to measuring who answers.
|
|
3292
|
+
*
|
|
3293
|
+
* **Shown only where the new default is doing the work.** A machine that answered yes on an earlier
|
|
3294
|
+
* version learns nothing from being told; a machine that answered no would be told something false.
|
|
3295
|
+
* Both are left alone, which is why the claim everywhere else is "every installation whose
|
|
3296
|
+
* behaviour changes is told once" rather than "every machine is told".
|
|
3303
3297
|
*
|
|
3304
|
-
*
|
|
3305
|
-
*
|
|
3306
|
-
*
|
|
3298
|
+
* **No terminal check.** The old question could not be asked without one. A disclosure has nobody
|
|
3299
|
+
* to wait for, and a headless install that transmits is exactly the install that must not be the
|
|
3300
|
+
* one nobody ever told. It costs a CI log three lines on a persisted home, once.
|
|
3307
3301
|
*/
|
|
3308
|
-
function
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3302
|
+
function noticeTelemetry(write) {
|
|
3303
|
+
if (process.env.PRB_TELEMETRY === "off") return;
|
|
3304
|
+
if (telemetryUploadConsent() !== null) return;
|
|
3305
|
+
if (telemetryNoticed()) return;
|
|
3306
|
+
write(telemetryNotice());
|
|
3307
|
+
recordTelemetryNotice();
|
|
3312
3308
|
}
|
|
3313
3309
|
//#endregion
|
|
3314
3310
|
//#region src/main.ts
|
|
@@ -3344,32 +3340,30 @@ function greet(command) {
|
|
|
3344
3340
|
recordGreeting();
|
|
3345
3341
|
}
|
|
3346
3342
|
/**
|
|
3347
|
-
* The one
|
|
3343
|
+
* The one-time telemetry disclosure, printed after the hello and before the work.
|
|
3348
3344
|
*
|
|
3349
|
-
* The greeting's commands minus `config`, which is where the
|
|
3350
|
-
* `config set telemetry-upload off`
|
|
3351
|
-
*
|
|
3345
|
+
* The greeting's commands minus `config`, which is where the setting is changed: somebody typing
|
|
3346
|
+
* `config set telemetry-upload off` should be able to turn it off without being told first that it
|
|
3347
|
+
* is on. That was true when this was a question and it is still true now it is a notice.
|
|
3352
3348
|
*
|
|
3353
|
-
* On stderr
|
|
3354
|
-
*
|
|
3349
|
+
* On stderr, like every other line here that is not a result, so `prreviewbuddy review | pbcopy`
|
|
3350
|
+
* still copies a URL. Unlike the question it replaced it takes no input and checks for no terminal:
|
|
3351
|
+
* there is nobody to wait for, and a headless install that transmits is the one that most needs
|
|
3352
|
+
* telling.
|
|
3355
3353
|
*/
|
|
3356
|
-
var
|
|
3354
|
+
var NOTIFIED = [
|
|
3357
3355
|
"review",
|
|
3358
3356
|
"open",
|
|
3359
3357
|
"agents"
|
|
3360
3358
|
];
|
|
3361
|
-
|
|
3362
|
-
if (!
|
|
3363
|
-
|
|
3364
|
-
isTTY: Boolean(process.stdin.isTTY && process.stderr.isTTY),
|
|
3365
|
-
input: process.stdin,
|
|
3366
|
-
write: (chunk) => void process.stderr.write(chunk)
|
|
3367
|
-
});
|
|
3359
|
+
function notify(command) {
|
|
3360
|
+
if (!NOTIFIED.includes(command.name)) return;
|
|
3361
|
+
noticeTelemetry((chunk) => void process.stderr.write(chunk));
|
|
3368
3362
|
}
|
|
3369
3363
|
async function main(argv) {
|
|
3370
3364
|
const command = parse(argv);
|
|
3371
3365
|
greet(command);
|
|
3372
|
-
|
|
3366
|
+
notify(command);
|
|
3373
3367
|
switch (command.name) {
|
|
3374
3368
|
case "help":
|
|
3375
3369
|
out(HELP);
|