prreviewbuddy 0.29.1 → 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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0
4
+
5
+ **Usage telemetry is now on by default.** Non-identifying usage telemetry is now enabled by default
6
+ on installations that have not previously made a choice. Existing opt-outs remain respected: if you
7
+ answered no when PR Review Buddy asked, nothing is sent, and nothing about your machine changes.
8
+ The first-run question is gone, replaced by a one-time notice in the terminal that says what is
9
+ sent and how to stop it. That notice is shown only to installations the new default actually
10
+ changes, so a machine that already answered sees nothing.
11
+
12
+ Nothing new is collected. The same two events carry the same eight fields they did before: review
13
+ start and finish, a random installation ID, the version, the platform, the agent, whether the
14
+ analysis succeeded and how long it took. Never your code, diffs, file paths, repository names,
15
+ prompts or review content. Rows are still deleted after 90 days.
16
+
17
+ `prreviewbuddy config set telemetry-upload off` turns it off, as does the Privacy section of the
18
+ workspace Settings page, and `PRB_TELEMETRY=off` still stops the local log as well. The reasoning
19
+ for the change, and what it replaced, is written out at https://prreviewbuddy.com/privacy.
20
+
21
+ **Copy says "non-identifying" rather than "anonymous".** The events carry a random installation ID
22
+ that persists, and a stable identifier makes data pseudonymous however little else is attached to
23
+ it. The word changed; what is sent did not.
24
+
3
25
  ## 0.29.1
4
26
 
5
27
  **Icons in the review sidebar.** Every item in a review's sidebar now has an icon: Review, KISS,
package/README.md CHANGED
@@ -44,10 +44,11 @@ an assistant that has read the diff. The job is durable, so closing the terminal
44
44
  Your code goes to the coding agent you chose, exactly as it would if you had run that agent
45
45
  yourself. It does not pass through us, and no server of ours is in the path of a review.
46
46
 
47
- PR Review Buddy asks once, on first run, whether it may send anonymous usage counts: two event
48
- names, a random ID generated on your machine, the version, the platform, the agent and a duration.
49
- Never your code, diffs, file paths, repository names, prompts or review content, and never anything
50
- at all unless you said yes. [The full field table is published](https://prreviewbuddy.com/privacy).
47
+ PR Review Buddy sends non-identifying usage telemetry, on by default and off with one command: two
48
+ event names, a random ID generated on your machine, the version, the platform, the agent and a
49
+ duration. Never your code, diffs, file paths, repository names, prompts or review content.
50
+ `prreviewbuddy config set telemetry-upload off` stops it.
51
+ [The full field table is published](https://prreviewbuddy.com/privacy).
51
52
 
52
53
  More at [prreviewbuddy.com](https://prreviewbuddy.com).
53
54
 
@@ -80,7 +81,7 @@ prreviewbuddy open <review> open one review
80
81
  prreviewbuddy agents which coding agents you have
81
82
  prreviewbuddy config set agent <agent> remember which to use when you do not name one
82
83
  prreviewbuddy config set telemetry-upload on|off
83
- send anonymous usage counts, or stop
84
+ send usage telemetry, or stop. On by default
84
85
 
85
86
  prreviewbuddy uninstall remove everything this put on your machine
86
87
  ```
@@ -167,10 +168,10 @@ ID** and **Delete review**, and the menu on a card deletes every review of that
167
168
  </details>
168
169
 
169
170
  <details>
170
- <summary><b>Anonymous usage counts</b> — what is sent, and how to stop it</summary>
171
+ <summary><b>Usage telemetry</b> — what is sent, and how to stop it</summary>
171
172
 
172
- Two events reach a server we run, and only if you answered yes when asked on first run:
173
- `review_started` and `review_finished`. Between them they carry a random UUID generated on your
173
+ Two events reach a server we run, on by default since 0.30.0: `review_started` and
174
+ `review_finished`. Between them they carry a random UUID generated on your
174
175
  machine, the CLI version, the platform, which agent ran the analysis, a timestamp, whether it
175
176
  succeeded and how long it took. That is the whole list, and the server refuses any field that is
176
177
  not on it rather than accepting the request and dropping what it does not recognise.
@@ -9298,26 +9298,32 @@ async function withUsageRecorded(workspaceId, kind, env, body, runId, requestedM
9298
9298
  */
9299
9299
  var UPLOAD_KEY = "telemetryUpload";
9300
9300
  /**
9301
- * The answer to the first-run question, or `null` if it has not been asked yet.
9301
+ * What this machine was told to do about transmission, or `null` if nobody ever said.
9302
9302
  *
9303
- * Three states rather than two, and the third is load-bearing: an install that has never been
9304
- * asked must be asked, and must not be treated as having declined. Collapsing `null` to `false`
9305
- * here would make the question unaskable the moment anything read the setting.
9303
+ * Three states rather than two, and the third is load-bearing in both directions: it is what lets
9304
+ * `upload.ts` apply a default without overwriting anybody's answer, and what lets the CLI disclose
9305
+ * the default to the machines it actually applies to and nobody else.
9306
9306
  */
9307
9307
  function telemetryUploadConsent() {
9308
9308
  return loadConfig().telemetryUpload;
9309
9309
  }
9310
- /** Whether a subset may go over the wire. Yes only if somebody said yes. */
9310
+ /**
9311
+ * Whether a subset may go over the wire. Yes unless somebody said no.
9312
+ *
9313
+ * `!== false` rather than `=== true`, and that is the whole of the 0.30.0 change in one operator.
9314
+ * Opt in measured who answers questions rather than who uses this: one or two installations
9315
+ * reporting against hundreds of weekly downloads. What is sent did not widen by a field.
9316
+ */
9311
9317
  function telemetryUploadEnabled() {
9312
9318
  const config = loadConfig();
9313
- return config.telemetry && config.telemetryUpload === true;
9319
+ return config.telemetry && config.telemetryUpload !== false;
9314
9320
  }
9315
9321
  /**
9316
- * Remember the answer, so the question is asked once on a machine and never again.
9322
+ * Remember a decision somebody made, so it outlives any change to the default.
9317
9323
  *
9318
9324
  * Read-modify-write through `config_file.ts`, and refuses outright while `PRB_TELEMETRY=off` is
9319
- * set: the kill switch means nothing is asked, so there is no answer to store, and writing one
9320
- * would record a decision nobody made.
9325
+ * set: the kill switch means nothing is being sent and nobody is being asked anything, so there is
9326
+ * no answer to store, and writing one would record a decision nobody made.
9321
9327
  */
9322
9328
  function recordTelemetryUploadConsent(answer) {
9323
9329
  if (process.env.PRB_TELEMETRY === "off") return;
@@ -9366,7 +9372,7 @@ function loadConfig() {
9366
9372
  } catch {}
9367
9373
  return config;
9368
9374
  }
9369
- /** Anything that is not exactly `true` or `false` is nobody having answered. */
9375
+ /** Anything that is not exactly `true` or `false` is nobody having chosen. */
9370
9376
  function consentOf(value) {
9371
9377
  return typeof value === "boolean" ? value : null;
9372
9378
  }
@@ -9415,7 +9421,7 @@ function project(record) {
9415
9421
  } : base;
9416
9422
  }
9417
9423
  /**
9418
- * Send one event, if there is one to send and somebody said it could be.
9424
+ * Send one event, if there is one to send and nobody has said not to.
9419
9425
  *
9420
9426
  * Returns nothing and awaits nothing. A failed send is a lost data point, which is a cost we
9421
9427
  * accept; a thrown error here would be a review that did not happen, which is not. The same
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
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, 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-DUCSC5Bc.js";
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 anonymous usage counts are sent
990
+ whether usage telemetry is sent. On by default
991
991
  prreviewbuddy config set telemetry-upload on|off
992
- send them, or stop
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 first-run question about transmission, in three lines.
1122
+ * The one-time disclosure, in four lines.
1123
1123
  *
1124
- * Three because a first run is not the place for a privacy policy, and the policy is one line
1125
- * away. What it names is what a person would otherwise have to assume: the list of things that are
1126
- * never sent is longer than the list of things that are, and printing the longer one is the honest
1127
- * way round.
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 telemetryUploadQuestion() {
1133
+ function telemetryNotice() {
1130
1134
  return [
1131
1135
  "",
1132
- "Help improve PR Review Buddy by sending anonymous usage counts?",
1133
- "Never your code, diffs, file paths, repository names, prompts or review content.",
1134
- "https://prreviewbuddy.com/privacy lists every field, exactly.",
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 has not been asked.
1145
+ * What `config get telemetry-upload` says, including for the machine that never chose.
1149
1146
  *
1150
- * Three answers rather than two, because "off" and "never asked" are different facts and only one
1151
- * of them is a decision somebody made.
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
- "Anonymous usage counts are not being sent, and nobody has been asked yet.",
1154
+ "Non-identifying usage telemetry is on by default, and you have not changed it.",
1156
1155
  "",
1157
- "The question is asked the first time you run PR Review Buddy in a terminal.",
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 ? "Anonymous usage counts are being sent. Turn them off with `prreviewbuddy config set telemetry-upload off`." : "Anonymous usage counts are not being sent.";
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 ? "Anonymous usage counts 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.";
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 two lines somebody needs the first time they run this, and never again.
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 MARKER = join(STORE_ROOT, "onboarding-shown");
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 greeted.
3246
+ * Whether this machine has been told that usage telemetry is on.
3237
3247
  *
3238
- * Unreadable reads as greeted. Getting this wrong in one direction repeats a banner, and in the
3239
- * other it prints one over somebody's output every single time, so the failure goes quiet.
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 greeted() {
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(MARKER);
3267
+ return existsSync(join(STORE_ROOT, name));
3244
3268
  } catch {
3245
3269
  return true;
3246
3270
  }
3247
3271
  }
3248
- function recordGreeting() {
3272
+ function record(name) {
3249
3273
  try {
3250
3274
  mkdirSync(STORE_ROOT, { recursive: true });
3251
- writeFileSync(MARKER, `${(/* @__PURE__ */ new Date()).toISOString()}\n`, { mode: 384 });
3275
+ writeFileSync(join(STORE_ROOT, name), `${(/* @__PURE__ */ new Date()).toISOString()}\n`, { mode: 384 });
3252
3276
  } catch {}
3253
3277
  }
3254
3278
  //#endregion
3255
- //#region src/telemetry_consent.ts
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
- * Store the answer, and say what was stored.
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
- * Said out loud in both directions, because a setting written to a file the person did not open is
3305
- * a change they need to know how to undo, and because "yes" and "nothing happened" look identical
3306
- * from a terminal otherwise.
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 settle(options, answer) {
3309
- recordTelemetryUploadConsent(answer);
3310
- options.write(`${answer ? telemetryUploadGranted() : telemetryUploadDeclined()}\n`);
3311
- return { kind: answer ? "granted" : "declined" };
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 question, asked after the hello and before the work.
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 answer is changed: somebody typing
3350
- * `config set telemetry-upload off` is answering the question, and prompting them for it first
3351
- * would be this binary interrupting the answer in order to ask.
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 with its input from stdin, like every other question here, so a piped stdout still
3354
- * carries only the result and a piped stdin is a run with nobody to ask.
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 ASKED = [
3354
+ var NOTIFIED = [
3357
3355
  "review",
3358
3356
  "open",
3359
3357
  "agents"
3360
3358
  ];
3361
- async function ask(command) {
3362
- if (!ASKED.includes(command.name)) return;
3363
- await askTelemetryUpload({
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
- await ask(command);
3366
+ notify(command);
3373
3367
  switch (command.name) {
3374
3368
  case "help":
3375
3369
  out(HELP);
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as fillFileUrlTemplate, $t as writeAgentPreference, A as removeWorktree, At as workspaceRevision, Bt as isQuestionOutstanding, C as recordTelemetryUploadConsent, Ct as previousKissResult, D as ReviewBeingDeletedError, Dt as saveWorkspace, E as EXPLAIN_SIMPLY_PROMPT, F as clearServerRecord, Ft as queueStamp, G as DEFAULT_WORKSPACE_PORT, Gt as describeAuthorship, H as BUILD_VERSION, Ht as questionsOutstanding, It as readQueue, J as parseWorkspacePort, Jt as git, K as MAX_WORKSPACE_PORT, L as readServerRecord, Lt as writeQueue, M as relativeTime$1, Mt as dequeueOnComplete, N as readIndexToken, Nt as queueAdd, O as MANAGED_ROOT, Pt as queueRemove, Qt as readAgentPreference, Rt as doneVerb, S as record, T as withUsageRecorded, Tt as reviewedCommit, Ut as reviewerDispositions, V as writeServerRecord, Vt as issuesOutstanding, W as feedbackUrl, Wt as processDiscussion, X as statedWorkspacePort, Xt as resolveModel, Y as resolveWorkspacePort, Z as writePortPreference, Zt as clearAgentPreference, _ as startKissJob, _t as latestKissRun, a as wasBlocked, an as agentFor, at as isTerminal, bt as loadWorkspace, c as updateReview, cn as AgentUnavailableError, d as askCheckout, dt as purposeOf, et as checkCodeFreshness, f as UpdateAlreadyRunningError, gt as indexReviewGroups, h as agentEnvOf, i as liveJobsFor, it as fail, jt as workspaceStamps, k as readMarker, kt as touchWorkspace, l as refreshPrContext, lt as phasesFor, mt as followedRefName, n as deleteReview, nn as AGENT_IDS, nt as allJobs, o as discardJob, ot as loadJob, p as liveUpdateFor, pt as isClaimed, q as MIN_WORKSPACE_PORT, r as lineageIds, rt as endedAt, s as RefreshUnavailableError, sn as AgentCancelledError, st as saveJob, t as deleteLineage, tn as STORE_ROOT, tt as checkFreshness, u as isUnchanged, ut as progressSteps, w as telemetryUploadConsent, wt as recentWorkspaces, x as readEvents, y as runJob, yt as lineagePosition, zt as isIssueOutstanding } from "./delete_review-DUCSC5Bc.js";
1
+ import { $ as fillFileUrlTemplate, $t as writeAgentPreference, A as removeWorktree, At as workspaceRevision, Bt as isQuestionOutstanding, C as recordTelemetryUploadConsent, Ct as previousKissResult, D as ReviewBeingDeletedError, Dt as saveWorkspace, E as EXPLAIN_SIMPLY_PROMPT, F as clearServerRecord, Ft as queueStamp, G as DEFAULT_WORKSPACE_PORT, Gt as describeAuthorship, H as BUILD_VERSION, Ht as questionsOutstanding, It as readQueue, J as parseWorkspacePort, Jt as git, K as MAX_WORKSPACE_PORT, L as readServerRecord, Lt as writeQueue, M as relativeTime$1, Mt as dequeueOnComplete, N as readIndexToken, Nt as queueAdd, O as MANAGED_ROOT, Pt as queueRemove, Qt as readAgentPreference, Rt as doneVerb, S as record, T as withUsageRecorded, Tt as reviewedCommit, Ut as reviewerDispositions, V as writeServerRecord, Vt as issuesOutstanding, W as feedbackUrl, Wt as processDiscussion, X as statedWorkspacePort, Xt as resolveModel, Y as resolveWorkspacePort, Z as writePortPreference, Zt as clearAgentPreference, _ as startKissJob, _t as latestKissRun, a as wasBlocked, an as agentFor, at as isTerminal, bt as loadWorkspace, c as updateReview, cn as AgentUnavailableError, d as askCheckout, dt as purposeOf, et as checkCodeFreshness, f as UpdateAlreadyRunningError, gt as indexReviewGroups, h as agentEnvOf, i as liveJobsFor, it as fail, jt as workspaceStamps, k as readMarker, kt as touchWorkspace, l as refreshPrContext, lt as phasesFor, mt as followedRefName, n as deleteReview, nn as AGENT_IDS, nt as allJobs, o as discardJob, ot as loadJob, p as liveUpdateFor, pt as isClaimed, q as MIN_WORKSPACE_PORT, r as lineageIds, rt as endedAt, s as RefreshUnavailableError, sn as AgentCancelledError, st as saveJob, t as deleteLineage, tn as STORE_ROOT, tt as checkFreshness, u as isUnchanged, ut as progressSteps, w as telemetryUploadConsent, wt as recentWorkspaces, x as readEvents, y as runJob, yt as lineagePosition, zt as isIssueOutstanding } from "./delete_review-B_2J2Wn9.js";
2
2
  import { isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
4
  import { createHash, timingSafeEqual } from "node:crypto";
@@ -5058,27 +5058,31 @@ function renderReviewsSection(settings) {
5058
5058
  </section>`;
5059
5059
  }
5060
5060
  /**
5061
- * The telemetry answer, visible.
5061
+ * The telemetry setting, visible.
5062
5062
  *
5063
5063
  * It has always been changeable, and only from the command line, which is a poor place for the one
5064
- * setting a person is most likely to want to check rather than to set. Three states rather than
5065
- * two: never having answered is not the same as having said no, and a page that showed it as "off"
5066
- * would be answering a question on somebody's behalf.
5064
+ * setting a person is most likely to want to check rather than to set.
5065
+ *
5066
+ * Two radios over three stored states. Until 0.30.0 the third state meant nothing was being sent,
5067
+ * so neither radio could be checked without answering on somebody's behalf. Now it means the
5068
+ * default is applying and the default is on, so Send it is checked and the help line says which of
5069
+ * the two reasons it is on for. The storage keeps all three, because a stored no has to outrank any
5070
+ * future change of default.
5067
5071
  */
5068
5072
  function renderPrivacySection(settings) {
5069
- const on = settings.usageCounts === true;
5070
5073
  const off = settings.usageCounts === false;
5071
5074
  return `<section class="prb-settings-section">
5072
5075
  <h2>Privacy</h2>
5073
5076
  <div class="prb-field prb-settings-field" role="group" aria-labelledby="prb-usage-label" aria-describedby="prb-usage-help">
5074
- <span id="prb-usage-label">Anonymous usage counts</span>
5077
+ <span id="prb-usage-label">Non-identifying usage telemetry</span>
5075
5078
  <div class="prb-settings-choice">
5076
- <label><input type="radio" name="prb-usage" value="on" data-settings-usage${on ? " checked" : ""}> Send them</label>
5077
- <label><input type="radio" name="prb-usage" value="off" data-settings-usage${off ? " checked" : ""}> Do not send them</label>
5079
+ <label><input type="radio" name="prb-usage" value="on" data-settings-usage${off ? "" : " checked"}> Send it</label>
5080
+ <label><input type="radio" name="prb-usage" value="off" data-settings-usage${off ? " checked" : ""}> Do not send it</label>
5078
5081
  </div>
5079
5082
  </div>
5080
- <p class="prb-settings-help" id="prb-usage-help">Counts only, never your code, your diffs, your
5081
- questions or the names of anything you review.${settings.usageCounts === null ? " You have not answered this yet, so nothing is being sent." : ""}</p>
5083
+ <p class="prb-settings-help" id="prb-usage-help">When a review starts and finishes, with the
5084
+ version, platform, agent, whether it succeeded and how long it took. Never your code, your diffs,
5085
+ your questions or the names of anything you review.${settings.usageCounts === null ? " It is on by default; you have not changed it." : ""}</p>
5082
5086
  </section>`;
5083
5087
  }
5084
5088
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prreviewbuddy",
3
- "version": "0.29.1",
3
+ "version": "0.30.0",
4
4
  "description": "Review a branch or pull request in an isolated checkout, from your terminal, with the coding agent you already have installed.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://prreviewbuddy.com",