prreviewbuddy 0.18.0 → 0.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/CHANGELOG.md +112 -0
- package/README.md +115 -45
- package/dist/main.js +226 -37
- package/dist/{relative_time-Ckbb4Srd.js → relative_time-BdTOYOad.js} +2792 -2272
- package/dist/server.js +215 -18
- package/package.json +6 -3
- package/static/chunk-B80S6zbH.js +4 -4
- package/static/chunk-B9H2wUlQ.js +1 -0
- package/static/reviews.js +1 -1
- package/static/shell.css +116 -14
- package/static/workspace.js +0 -0
package/dist/main.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { A as startJob, C as updateReview, Ct as git, Dt as STORE_ROOT, Et as writeConfigValues, F as BUILD_VERSION, G as readMarker, H as clearClaim, I as PACKAGE_NAME, J as WORK_PHASES, K as removeWorktree, M as record, N as recordTelemetryUploadConsent, O as reanalyseReview, Ot as AGENT_IDS, P as telemetryUploadConsent, Q as loadJob, St as displayRef, Tt as readConfigFile, U as isClaimed, W as MANAGED_ROOT, Y as allJobs, Z as isTerminal, a as liveJobsFor, at as positionInLineage, d as ensureServer, dt as summarise, et as followedRefName, f as readServerRecord, h as workspaceUrl, it as loadWorkspace, jt as detectAgents, k as runJob, kt as agentById, l as bootstrapUrl, lt as reviewedRepositories, m as stopServer, nt as lineageKeyFor, o as wasBlocked, p as reviewsUrl, q as checkFreshness, r as deleteReview$1, s as runningJobs, st as recentWorkspaces, t as relativeTime, tt as groupByLineage, ut as saveWorkspace, wt as agentIdOf, xt as resolveTarget, y as LANDING_PAGE, z as forgeResolver } from "./relative_time-BdTOYOad.js";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { existsSync, mkdirSync,
|
|
5
|
+
import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { hostname } from "node:os";
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
8
8
|
import { platform } from "node:process";
|
|
@@ -250,18 +250,13 @@ function unknownCommand(head) {
|
|
|
250
250
|
* No validation of the id here, deliberately. A person may configure an agent before installing it,
|
|
251
251
|
* and this module has no business knowing which ids exist: the registry does, and the CLI edge is
|
|
252
252
|
* where an unknown one is refused with a list of the real ones.
|
|
253
|
+
*
|
|
254
|
+
* The read-modify-write that keeps everybody else's keys used to live here. It is in
|
|
255
|
+
* `config_file.ts` now, because being the only careful writer of a shared file is not a property
|
|
256
|
+
* one module can hold.
|
|
253
257
|
*/
|
|
254
|
-
var CONFIG_PATH = join(STORE_ROOT, "config.json");
|
|
255
258
|
/** The key this module owns. Everything else in the file belongs to somebody else and is kept. */
|
|
256
259
|
var KEY = "agent";
|
|
257
|
-
function readConfig() {
|
|
258
|
-
try {
|
|
259
|
-
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
260
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
261
|
-
} catch {
|
|
262
|
-
return {};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
260
|
/**
|
|
266
261
|
* The configured default, or nothing.
|
|
267
262
|
*
|
|
@@ -271,25 +266,11 @@ function readConfig() {
|
|
|
271
266
|
* lie if this invented one.
|
|
272
267
|
*/
|
|
273
268
|
function readAgentPreference() {
|
|
274
|
-
const value =
|
|
269
|
+
const value = readConfigFile()[KEY];
|
|
275
270
|
return typeof value === "string" && value !== "" ? value : null;
|
|
276
271
|
}
|
|
277
|
-
/**
|
|
278
|
-
* Remember an agent, keeping every setting this module does not own.
|
|
279
|
-
*
|
|
280
|
-
* Read-modify-write rather than a bare write. `config.json` holds `editor` and whatever else the
|
|
281
|
-
* reviewer has put there, and a preference that quietly dropped their editor while saving their
|
|
282
|
-
* agent would be a worse bargain than not remembering anything.
|
|
283
|
-
*/
|
|
284
272
|
function writeAgentPreference(id) {
|
|
285
|
-
|
|
286
|
-
...readConfig(),
|
|
287
|
-
[KEY]: id
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
function save(config) {
|
|
291
|
-
mkdirSync(STORE_ROOT, { recursive: true });
|
|
292
|
-
writeFileSync(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
|
|
273
|
+
writeConfigValues({ [KEY]: id });
|
|
293
274
|
}
|
|
294
275
|
//#endregion
|
|
295
276
|
//#region ../../packages/review-harness/src/agents/resolve.ts
|
|
@@ -665,6 +646,10 @@ your own checkout is never read or written while it works.
|
|
|
665
646
|
prreviewbuddy agents <agent> show details for ${AGENT_NAMES}
|
|
666
647
|
prreviewbuddy config get agent the coding agent used when you do not name one
|
|
667
648
|
prreviewbuddy config set agent <agent> remember ${AGENT_NAMES}
|
|
649
|
+
prreviewbuddy config get telemetry-upload
|
|
650
|
+
whether anonymous usage counts are sent
|
|
651
|
+
prreviewbuddy config set telemetry-upload on|off
|
|
652
|
+
send them, or stop
|
|
668
653
|
|
|
669
654
|
prreviewbuddy uninstall remove everything this put on your machine
|
|
670
655
|
prreviewbuddy uninstall --keep-reviews keep your reviews and settings, clear the rest
|
|
@@ -773,6 +758,53 @@ function unknownSetting(key, known) {
|
|
|
773
758
|
return `There is no setting called \`${key}\`. This can read and set: ${known.join(", ")}.`;
|
|
774
759
|
}
|
|
775
760
|
/**
|
|
761
|
+
* The first-run question about transmission, in three lines.
|
|
762
|
+
*
|
|
763
|
+
* Three because a first run is not the place for a privacy policy, and the policy is one line
|
|
764
|
+
* away. What it names is what a person would otherwise have to assume: the list of things that are
|
|
765
|
+
* never sent is longer than the list of things that are, and printing the longer one is the honest
|
|
766
|
+
* way round.
|
|
767
|
+
*/
|
|
768
|
+
function telemetryUploadQuestion() {
|
|
769
|
+
return [
|
|
770
|
+
"",
|
|
771
|
+
"Help improve PR Review Buddy by sending anonymous usage counts?",
|
|
772
|
+
"Never your code, diffs, file paths, repository names, prompts or review content.",
|
|
773
|
+
"https://prreviewbuddy.com/privacy lists every field, exactly.",
|
|
774
|
+
""
|
|
775
|
+
].join("\n");
|
|
776
|
+
}
|
|
777
|
+
function telemetryUploadUnclear() {
|
|
778
|
+
return "Please answer y or n.\n";
|
|
779
|
+
}
|
|
780
|
+
function telemetryUploadGranted() {
|
|
781
|
+
return "Thank you. Turn it off whenever you like with `prreviewbuddy config set telemetry-upload off`.";
|
|
782
|
+
}
|
|
783
|
+
function telemetryUploadDeclined() {
|
|
784
|
+
return "Nothing will be sent. Turn it on with `prreviewbuddy config set telemetry-upload on`.";
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* What `config get telemetry-upload` says, including for the machine that has not been asked.
|
|
788
|
+
*
|
|
789
|
+
* Three answers rather than two, because "off" and "never asked" are different facts and only one
|
|
790
|
+
* of them is a decision somebody made.
|
|
791
|
+
*/
|
|
792
|
+
function telemetryUploadIs(consent) {
|
|
793
|
+
if (consent === null) return [
|
|
794
|
+
"Anonymous usage counts are not being sent, and nobody has been asked yet.",
|
|
795
|
+
"",
|
|
796
|
+
"The question is asked the first time you run PR Review Buddy in a terminal.",
|
|
797
|
+
"Answer it now with `prreviewbuddy config set telemetry-upload on` or `off`."
|
|
798
|
+
].join("\n");
|
|
799
|
+
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.";
|
|
800
|
+
}
|
|
801
|
+
function telemetryUploadSet(on) {
|
|
802
|
+
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.";
|
|
803
|
+
}
|
|
804
|
+
function unknownSettingValue(key, value, known) {
|
|
805
|
+
return `\`${key}\` cannot be set to \`${value}\`. It takes: ${known.join(", ")}.`;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
776
808
|
* What to say when `resolveAgent` could not resolve one.
|
|
777
809
|
*
|
|
778
810
|
* Each outcome gets its own sentence rather than a shared "could not choose an agent", because the
|
|
@@ -1087,6 +1119,17 @@ var DELETE_QUESTION = "Delete it?";
|
|
|
1087
1119
|
function deleteUnattended(id) {
|
|
1088
1120
|
return `Deleting a review asks for confirmation, and there is nothing here to ask. Run \`prreviewbuddy review --review ${id} --delete --yes\` if you meant it.`;
|
|
1089
1121
|
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Something else held the review for the moment the deletion asked for it.
|
|
1124
|
+
*
|
|
1125
|
+
* Not `deleteRunning`, which names a branch and an analysis, because this is the case where there
|
|
1126
|
+
* is nothing to name: another surface deleting the same card, or a job being created in the same
|
|
1127
|
+
* instant. "Try again" is honest here and would not be there -- an analysis takes minutes and this
|
|
1128
|
+
* takes milliseconds.
|
|
1129
|
+
*/
|
|
1130
|
+
function deleteBusy() {
|
|
1131
|
+
return "This review is being worked on right now, so nothing was deleted. Try again in a moment.";
|
|
1132
|
+
}
|
|
1090
1133
|
function deleteCancelled() {
|
|
1091
1134
|
return "Nothing was deleted.";
|
|
1092
1135
|
}
|
|
@@ -1234,7 +1277,12 @@ async function deleteReview(command, deps = REAL$1) {
|
|
|
1234
1277
|
return 1;
|
|
1235
1278
|
}
|
|
1236
1279
|
}
|
|
1237
|
-
|
|
1280
|
+
const report = await deps.remove(id);
|
|
1281
|
+
if (wasBlocked(report)) {
|
|
1282
|
+
err(report.live.length > 0 ? deleteRunning(report.live[0]?.job.target.branch ?? workspace.changeSet.headRef) : deleteBusy());
|
|
1283
|
+
return 1;
|
|
1284
|
+
}
|
|
1285
|
+
out(deleteFinished(report));
|
|
1238
1286
|
return 0;
|
|
1239
1287
|
}
|
|
1240
1288
|
//#endregion
|
|
@@ -1328,6 +1376,7 @@ function phaseLines(job, colour) {
|
|
|
1328
1376
|
const lines = [];
|
|
1329
1377
|
const failedAt = job.phase === "failed" ? job.failure?.phase : void 0;
|
|
1330
1378
|
for (const phase of WORK_PHASES) {
|
|
1379
|
+
if (!applies(job, phase)) continue;
|
|
1331
1380
|
const complete = job.completed.includes(phase) || job.phase === "done";
|
|
1332
1381
|
const failed = failedAt === phase;
|
|
1333
1382
|
const current = !complete && !failed && job.phase === phase;
|
|
@@ -1343,6 +1392,31 @@ function phaseLines(job, colour) {
|
|
|
1343
1392
|
}
|
|
1344
1393
|
return lines;
|
|
1345
1394
|
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Whether a phase is part of *this* review, rather than part of the pipeline in general.
|
|
1397
|
+
*
|
|
1398
|
+
* Only `conversation` is ever optional, and reviewing a branch with no pull request is an ordinary
|
|
1399
|
+
* supported thing to do rather than a degraded version of something else. Ticking "Read pull
|
|
1400
|
+
* request conversation" on such a review claims a step that did not happen and makes a complete
|
|
1401
|
+
* review look like an incomplete one; printing "No pull request found" instead is no better,
|
|
1402
|
+
* because a line in a checklist reads as something missing whatever words are in it.
|
|
1403
|
+
*
|
|
1404
|
+
* So the rule is that an optional stage which did not apply is not rendered at all:
|
|
1405
|
+
*
|
|
1406
|
+
* - A pull request review always shows it. It is the point of that review, and it is known before
|
|
1407
|
+
* the phase runs, so the line never appears late.
|
|
1408
|
+
* - A branch review shows it only once a conversation was actually attached, which happens when the
|
|
1409
|
+
* branch turns out to have a request on a forge we read. Real work, worth saying.
|
|
1410
|
+
* - Everything else — no request, an unsupported host, `gh` missing — shows nothing.
|
|
1411
|
+
*
|
|
1412
|
+
* The absent cases are still explained, once, at the end: the closing summary says the review was
|
|
1413
|
+
* made without the conversation and why. That is the right place for it, because by then it is a
|
|
1414
|
+
* fact about the finished review rather than a gap in a list of things still happening.
|
|
1415
|
+
*/
|
|
1416
|
+
function applies(job, phase) {
|
|
1417
|
+
if (phase !== "conversation") return true;
|
|
1418
|
+
return job.target.pullRequest !== void 0 || job.conversation?.kind === "attached";
|
|
1419
|
+
}
|
|
1346
1420
|
/** Same words as the phase heading, give or take the articles and the trailing full stop. */
|
|
1347
1421
|
function restates(note, label) {
|
|
1348
1422
|
const bare = (text) => text.toLowerCase().replace(/\bthe\b/g, "").replace(/[^a-z]/g, "");
|
|
@@ -1787,19 +1861,27 @@ async function agents(command) {
|
|
|
1787
1861
|
//#endregion
|
|
1788
1862
|
//#region src/commands/config.ts
|
|
1789
1863
|
/**
|
|
1790
|
-
* `prreviewbuddy config get
|
|
1864
|
+
* `prreviewbuddy config get <key>` and `config set <key> <value>`.
|
|
1791
1865
|
*
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1866
|
+
* Two settings now, which is what the shape was always for: reading and writing one key by name
|
|
1867
|
+
* survives a second setting, and a `default-agent` command would not have.
|
|
1868
|
+
*
|
|
1869
|
+
* The keys are hyphenated at the command line and camel-cased in `config.json`, and the difference
|
|
1870
|
+
* is deliberate rather than an oversight. `telemetryUpload` is the storage key and stays put; what
|
|
1871
|
+
* somebody types is `telemetry-upload`, because that is how every other flag on this binary reads.
|
|
1795
1872
|
*/
|
|
1796
1873
|
/** The keys this command owns. Anything else is refused by name rather than silently stored. */
|
|
1797
|
-
var KEYS = ["agent"];
|
|
1874
|
+
var KEYS = ["agent", "telemetry-upload"];
|
|
1875
|
+
/** What the second one takes. A boolean setting, so a closed pair rather than anything truthy. */
|
|
1876
|
+
var SWITCH_VALUES = ["on", "off"];
|
|
1798
1877
|
function config(command) {
|
|
1799
1878
|
if (!KEYS.includes(command.key)) {
|
|
1800
1879
|
err(unknownSetting(command.key, KEYS));
|
|
1801
1880
|
return 1;
|
|
1802
1881
|
}
|
|
1882
|
+
return command.key === "agent" ? agent(command) : telemetryUpload(command);
|
|
1883
|
+
}
|
|
1884
|
+
function agent(command) {
|
|
1803
1885
|
if (command.action === "get") {
|
|
1804
1886
|
const preferred = readAgentPreference();
|
|
1805
1887
|
out(preferred ? agentPreferenceIs(preferred) : noAgentPreference());
|
|
@@ -1814,6 +1896,28 @@ function config(command) {
|
|
|
1814
1896
|
out(agentPreferenceSet(id));
|
|
1815
1897
|
return 0;
|
|
1816
1898
|
}
|
|
1899
|
+
/**
|
|
1900
|
+
* The answer to the first-run question, readable and changeable afterwards.
|
|
1901
|
+
*
|
|
1902
|
+
* This is the only way to say yes without being asked, and the only way to change the answer
|
|
1903
|
+
* without editing a file by hand. Both directions matter: a person who declined at a prompt they
|
|
1904
|
+
* were half reading needs a way back, and a person who agreed needs one out.
|
|
1905
|
+
*/
|
|
1906
|
+
function telemetryUpload(command) {
|
|
1907
|
+
if (command.action === "get") {
|
|
1908
|
+
out(telemetryUploadIs(telemetryUploadConsent()));
|
|
1909
|
+
return 0;
|
|
1910
|
+
}
|
|
1911
|
+
const value = command.value.toLowerCase();
|
|
1912
|
+
if (!SWITCH_VALUES.includes(value)) {
|
|
1913
|
+
err(unknownSettingValue("telemetry-upload", command.value, SWITCH_VALUES));
|
|
1914
|
+
return 1;
|
|
1915
|
+
}
|
|
1916
|
+
const on = value === "on";
|
|
1917
|
+
recordTelemetryUploadConsent(on);
|
|
1918
|
+
out(telemetryUploadSet(on));
|
|
1919
|
+
return 0;
|
|
1920
|
+
}
|
|
1817
1921
|
//#endregion
|
|
1818
1922
|
//#region ../../packages/review-harness/src/workspace/uninstall.ts
|
|
1819
1923
|
/**
|
|
@@ -2008,10 +2112,12 @@ function isInside(path, root) {
|
|
|
2008
2112
|
* round deletes somebody's data to satisfy a list written before the data existed.
|
|
2009
2113
|
*
|
|
2010
2114
|
* So what stays is everything else, and today that means their reviews (`workspaces`,
|
|
2011
|
-
* `reviews.html`), their settings (`config.json`, `prompts.json`) and the
|
|
2012
|
-
*
|
|
2013
|
-
*
|
|
2014
|
-
*
|
|
2115
|
+
* `reviews.html`), their settings (`config.json`, `prompts.json`) and the record of having been
|
|
2116
|
+
* greeted once (`onboarding-shown`). That last one is not runtime state: it is the same person on
|
|
2117
|
+
* the same machine, they have read the banner, and showing it again would be this product
|
|
2118
|
+
* forgetting a conversation rather than clearing one. The answer to the telemetry question is in
|
|
2119
|
+
* `config.json` with the rest of the settings, for the same reason and a stronger one: re-asking a
|
|
2120
|
+
* question somebody has already answered is worse than repeating a notice they have already read.
|
|
2015
2121
|
* The usage log itself (`events.jsonl`) does go, because that is data about them rather than a note
|
|
2016
2122
|
* to ourselves.
|
|
2017
2123
|
*/
|
|
@@ -2174,6 +2280,65 @@ function recordGreeting() {
|
|
|
2174
2280
|
} catch {}
|
|
2175
2281
|
}
|
|
2176
2282
|
//#endregion
|
|
2283
|
+
//#region src/telemetry_consent.ts
|
|
2284
|
+
/**
|
|
2285
|
+
* The one question this product asks about itself, asked once per machine.
|
|
2286
|
+
*
|
|
2287
|
+
* `packages/review-harness/src/telemetry/` has recorded usage events to a local file since it
|
|
2288
|
+
* existed, disclosed and on by default. Sending a subset of them to a server we run is a different
|
|
2289
|
+
* act, and `apps/website/src/pages/Privacy.tsx` promised in as many words that if it ever happened
|
|
2290
|
+
* it would be opt in and off by default. This is how that promise is kept.
|
|
2291
|
+
*
|
|
2292
|
+
* Hand-rolled on `node:readline` beside `select.ts` and `confirm.ts`, for the reason they both
|
|
2293
|
+
* give: this binary has no runtime dependencies and one question is not worth acquiring one. The io
|
|
2294
|
+
* is injected for the same reason theirs is, so what the answer means is testable without a shell.
|
|
2295
|
+
*
|
|
2296
|
+
* **A bare Return re-asks rather than declining.** `confirm.ts` next door defaults to no on Return,
|
|
2297
|
+
* which is right there because every one of its callers is about to delete something. Here it would
|
|
2298
|
+
* be wrong in a subtler way: "no default" was the decision, and a capitalised `N` in the brackets
|
|
2299
|
+
* turns it into opt-out-by-keystroke, which is a different thing wearing the same words. Somebody
|
|
2300
|
+
* who has not read the question has not answered it.
|
|
2301
|
+
*
|
|
2302
|
+
* **No terminal means no question and no answer.** A durable job, a CI run and a piped invocation
|
|
2303
|
+
* must not block on a question nobody can see, and must not be recorded as having declined one:
|
|
2304
|
+
* the setting stays unset, nothing is sent, and the same machine is asked properly the first time
|
|
2305
|
+
* a person runs this interactively.
|
|
2306
|
+
*/
|
|
2307
|
+
async function askTelemetryUpload(options) {
|
|
2308
|
+
const stored = telemetryUploadConsent();
|
|
2309
|
+
if (stored !== null) return { kind: stored ? "granted" : "declined" };
|
|
2310
|
+
if (!options.isTTY) return { kind: "unanswered" };
|
|
2311
|
+
const reader = createInterface({ input: options.input });
|
|
2312
|
+
const lines = reader[Symbol.asyncIterator]();
|
|
2313
|
+
try {
|
|
2314
|
+
options.write(`${telemetryUploadQuestion()}\n`);
|
|
2315
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2316
|
+
options.write("[y/n] ");
|
|
2317
|
+
const next = await lines.next();
|
|
2318
|
+
if (next.done) return { kind: "unanswered" };
|
|
2319
|
+
const answer = next.value.trim().toLowerCase();
|
|
2320
|
+
if (answer === "y" || answer === "yes") return settle(options, true);
|
|
2321
|
+
if (answer === "n" || answer === "no") return settle(options, false);
|
|
2322
|
+
options.write(telemetryUploadUnclear());
|
|
2323
|
+
}
|
|
2324
|
+
return { kind: "unanswered" };
|
|
2325
|
+
} finally {
|
|
2326
|
+
reader.close();
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Store the answer, and say what was stored.
|
|
2331
|
+
*
|
|
2332
|
+
* Said out loud in both directions, because a setting written to a file the person did not open is
|
|
2333
|
+
* a change they need to know how to undo, and because "yes" and "nothing happened" look identical
|
|
2334
|
+
* from a terminal otherwise.
|
|
2335
|
+
*/
|
|
2336
|
+
function settle(options, answer) {
|
|
2337
|
+
recordTelemetryUploadConsent(answer);
|
|
2338
|
+
options.write(`${answer ? telemetryUploadGranted() : telemetryUploadDeclined()}\n`);
|
|
2339
|
+
return { kind: answer ? "granted" : "declined" };
|
|
2340
|
+
}
|
|
2341
|
+
//#endregion
|
|
2177
2342
|
//#region src/main.ts
|
|
2178
2343
|
/**
|
|
2179
2344
|
* PR Review Buddy at the command line.
|
|
@@ -2206,9 +2371,33 @@ function greet(command) {
|
|
|
2206
2371
|
err(firstRun(BUILD_VERSION));
|
|
2207
2372
|
recordGreeting();
|
|
2208
2373
|
}
|
|
2374
|
+
/**
|
|
2375
|
+
* The one question, asked after the hello and before the work.
|
|
2376
|
+
*
|
|
2377
|
+
* The greeting's commands minus `config`, which is where the answer is changed: somebody typing
|
|
2378
|
+
* `config set telemetry-upload off` is answering the question, and prompting them for it first
|
|
2379
|
+
* would be this binary interrupting the answer in order to ask.
|
|
2380
|
+
*
|
|
2381
|
+
* On stderr with its input from stdin, like every other question here, so a piped stdout still
|
|
2382
|
+
* carries only the result and a piped stdin is a run with nobody to ask.
|
|
2383
|
+
*/
|
|
2384
|
+
var ASKED = [
|
|
2385
|
+
"review",
|
|
2386
|
+
"open",
|
|
2387
|
+
"agents"
|
|
2388
|
+
];
|
|
2389
|
+
async function ask(command) {
|
|
2390
|
+
if (!ASKED.includes(command.name)) return;
|
|
2391
|
+
await askTelemetryUpload({
|
|
2392
|
+
isTTY: Boolean(process.stdin.isTTY && process.stderr.isTTY),
|
|
2393
|
+
input: process.stdin,
|
|
2394
|
+
write: (chunk) => void process.stderr.write(chunk)
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2209
2397
|
async function main(argv) {
|
|
2210
2398
|
const command = parse(argv);
|
|
2211
2399
|
greet(command);
|
|
2400
|
+
await ask(command);
|
|
2212
2401
|
switch (command.name) {
|
|
2213
2402
|
case "help":
|
|
2214
2403
|
out(HELP);
|