@haystackeditor/cli 0.15.4 → 0.15.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/setup.js +42 -242
- package/dist/utils/prompter.js +6 -6
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -125,7 +125,7 @@ const CONFIG_COMMIT_MSG = 'chore: configure Haystack via CLI setup wizard';
|
|
|
125
125
|
const AUTO_MERGE_LABEL = 'haystack:auto-merge';
|
|
126
126
|
const ONBOARDING_BOOTSTRAP_LABEL = 'haystack:onboarding-bootstrap';
|
|
127
127
|
const BOOTSTRAP_PR_TITLE = 'Configure Haystack';
|
|
128
|
-
const BOOTSTRAP_PR_BODY_INTRO = 'This PR was opened automatically by the Haystack CLI setup wizard
|
|
128
|
+
const BOOTSTRAP_PR_BODY_INTRO = 'This PR was opened automatically by the Haystack CLI setup wizard to configure Haystack for this repo.\n\n' +
|
|
129
129
|
'It adds the Haystack onboarding config:\n' +
|
|
130
130
|
'- `.haystack/pr-rules.yml` — rules Haystack enforces on every PR\n' +
|
|
131
131
|
'- `.haystack/review-policy.md` — path-scoped review policies + review instructions\n' +
|
|
@@ -146,47 +146,6 @@ const BOOTSTRAP_PR_OUTRO_MANUAL = 'Auto-merge is off for this repo, so this PR i
|
|
|
146
146
|
function encodeBranchPath(branch) {
|
|
147
147
|
return branch.split('/').map(encodeURIComponent).join('/');
|
|
148
148
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Is the given branch protected such that a direct commit can't land —
|
|
151
|
-
* either by legacy branch protection OR a repository ruleset?
|
|
152
|
-
*
|
|
153
|
-
* Decided from GitHub's *structured* signals, not error-message text:
|
|
154
|
-
* - `GET /repos/{o}/{r}/branches/{b}` → `.protected` (legacy protection)
|
|
155
|
-
* - `GET /repos/{o}/{r}/rules/branches/{b}` → a `pull_request` rule means
|
|
156
|
-
* "changes must go through a PR" (rulesets)
|
|
157
|
-
*
|
|
158
|
-
* Called on the write-failure path: a 409/422 from the direct commit tells
|
|
159
|
-
* us *that* something rejected the write; this tells us *whether* it was
|
|
160
|
-
* protection (→ open a bootstrap PR) vs. a genuine bad request (→ surface
|
|
161
|
-
* the error). If the probe itself can't be completed we throw — guessing
|
|
162
|
-
* from message wording is exactly the brittleness we're avoiding here.
|
|
163
|
-
*/
|
|
164
|
-
async function isBranchProtected(owner, repo, branch, token) {
|
|
165
|
-
const headers = ghHeaders(token);
|
|
166
|
-
const enc = encodeBranchPath(branch);
|
|
167
|
-
// Legacy branch protection — the `protected` boolean is authoritative.
|
|
168
|
-
const branchRes = await fetch(`${GITHUB_PROXY}/repos/${owner}/${repo}/branches/${enc}`, { headers });
|
|
169
|
-
if (branchRes.ok) {
|
|
170
|
-
const data = (await branchRes.json());
|
|
171
|
-
if (data.protected === true)
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
else if (branchRes.status !== 404) {
|
|
175
|
-
throw new Error(`Couldn't check branch protection: ${branchRes.status} — ${await branchRes.text()}`);
|
|
176
|
-
}
|
|
177
|
-
// Repository rulesets — `rules/branches` collapses every active rule on
|
|
178
|
-
// the branch into one list. A `pull_request` rule = "must merge via PR".
|
|
179
|
-
const rulesRes = await fetch(`${GITHUB_PROXY}/repos/${owner}/${repo}/rules/branches/${enc}`, { headers });
|
|
180
|
-
if (rulesRes.ok) {
|
|
181
|
-
const rules = (await rulesRes.json());
|
|
182
|
-
if (rules.some((r) => r.type === 'pull_request'))
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
else if (rulesRes.status !== 404) {
|
|
186
|
-
throw new Error(`Couldn't check repository rulesets: ${rulesRes.status} — ${await rulesRes.text()}`);
|
|
187
|
-
}
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
149
|
function ghHeaders(token) {
|
|
191
150
|
return {
|
|
192
151
|
Authorization: `Bearer ${token}`,
|
|
@@ -381,56 +340,6 @@ async function addBootstrapLabels(owner, repo, prNumber, prUrl, token, autoMerge
|
|
|
381
340
|
`Add these labels to the PR manually so Haystack picks it up: ${labels.join(', ')}. ` +
|
|
382
341
|
`Don't re-run \`haystack setup\` — that opens a duplicate PR.`);
|
|
383
342
|
}
|
|
384
|
-
/** Wraps a contents-API write failure with its HTTP status so the caller can
|
|
385
|
-
* distinguish a protection rejection (409/422 → PR fallback) from a genuine
|
|
386
|
-
* error (→ surface it). */
|
|
387
|
-
class GitHubWriteError extends Error {
|
|
388
|
-
status;
|
|
389
|
-
body;
|
|
390
|
-
constructor(status, body) {
|
|
391
|
-
super(`GitHub write failed: ${status}`);
|
|
392
|
-
this.status = status;
|
|
393
|
-
this.body = body;
|
|
394
|
-
this.name = 'GitHubWriteError';
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
/** Read the blob SHA of an existing file (needed to update it). Non-OK (incl.
|
|
398
|
-
* 404 = file doesn't exist yet) returns null; the subsequent PUT then creates
|
|
399
|
-
* the file, and surfaces a real error if the write itself fails. */
|
|
400
|
-
async function fetchFileSha(owner, repo, path, token) {
|
|
401
|
-
const res = await fetch(`${GITHUB_PROXY}/repos/${owner}/${repo}/contents/${path}`, { headers: ghHeaders(token) });
|
|
402
|
-
if (!res.ok) {
|
|
403
|
-
if (res.status !== 404) {
|
|
404
|
-
trackError('haystack_setup_fetch_file_sha_failed', {
|
|
405
|
-
owner,
|
|
406
|
-
repo,
|
|
407
|
-
path,
|
|
408
|
-
status: res.status,
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
const data = (await res.json());
|
|
414
|
-
return data.sha ?? null;
|
|
415
|
-
}
|
|
416
|
-
/** PUT a single file to the default branch via the contents API. Throws
|
|
417
|
-
* GitHubWriteError on any non-OK response so the caller can branch on status. */
|
|
418
|
-
async function putFile(owner, repo, path, content, message, sha, token) {
|
|
419
|
-
const body = {
|
|
420
|
-
message,
|
|
421
|
-
content: Buffer.from(content).toString('base64'),
|
|
422
|
-
};
|
|
423
|
-
if (sha)
|
|
424
|
-
body.sha = sha;
|
|
425
|
-
const res = await fetch(`${GITHUB_PROXY}/repos/${owner}/${repo}/contents/${path}`, {
|
|
426
|
-
method: 'PUT',
|
|
427
|
-
headers: ghHeaders(token),
|
|
428
|
-
body: JSON.stringify(body),
|
|
429
|
-
});
|
|
430
|
-
if (!res.ok) {
|
|
431
|
-
throw new GitHubWriteError(res.status, await res.text().catch(() => ''));
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
343
|
/**
|
|
435
344
|
* Protected-branch fallback: build ONE commit containing ALL the onboarding
|
|
436
345
|
* files (`configFiles` + `.entire/settings.json`) via the Git Data API, CREATE
|
|
@@ -495,50 +404,17 @@ async function openBootstrapConfigPR(owner, repo, configFiles, token, defaultBra
|
|
|
495
404
|
return { prUrl: pr.html_url, entireSettingsBundled };
|
|
496
405
|
}
|
|
497
406
|
/**
|
|
498
|
-
* Write the onboarding config files to a repo
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* Returns `{ bootstrapPR }` when the PR fallback was used (so the caller can
|
|
505
|
-
* thread the branch into step 7), `{}` on clean direct commits.
|
|
407
|
+
* Write the onboarding config files to a repo by ALWAYS opening a PR
|
|
408
|
+
* (branch → one commit → PR) — never a direct commit to the default branch.
|
|
409
|
+
* Matches the web wizard (#1936): a silent push to someone's default branch is
|
|
410
|
+
* jarring and hard to undo, so onboarding always goes through a "Configure
|
|
411
|
+
* Haystack" PR. The PR is enrolled for auto-merge only when the user kept
|
|
412
|
+
* auto-merge on; otherwise it waits for them to review and merge it.
|
|
506
413
|
*/
|
|
507
414
|
async function writeOnboardingFilesToRepo(owner, repo, files, autoMergeEnabled, token) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const shaByPath = new Map();
|
|
512
|
-
for (const f of files) {
|
|
513
|
-
shaByPath.set(f.path, await fetchFileSha(owner, repo, f.path, token));
|
|
514
|
-
}
|
|
515
|
-
let successfulWrites = 0;
|
|
516
|
-
try {
|
|
517
|
-
for (const f of files) {
|
|
518
|
-
await putFile(owner, repo, f.path, f.content, f.message, shaByPath.get(f.path) ?? null, token);
|
|
519
|
-
successfulWrites++;
|
|
520
|
-
}
|
|
521
|
-
return {};
|
|
522
|
-
}
|
|
523
|
-
catch (err) {
|
|
524
|
-
// Only a 409/422 can be a protection rejection. Anything else (auth, 404,
|
|
525
|
-
// 5xx, network) is a genuine error — surface it.
|
|
526
|
-
const status = err instanceof GitHubWriteError ? err.status : 0;
|
|
527
|
-
if (status !== 409 && status !== 422)
|
|
528
|
-
throw err;
|
|
529
|
-
if (successfulWrites > 0) {
|
|
530
|
-
throw err;
|
|
531
|
-
}
|
|
532
|
-
// Probe GitHub's *structured* protection signals to decide: protection
|
|
533
|
-
// (→ PR fallback) vs. a genuine bad request / sha conflict (→ surface the
|
|
534
|
-
// error). This replaces brittle error-message substring matching.
|
|
535
|
-
const defaultBranch = await getDefaultBranch(owner, repo, token);
|
|
536
|
-
if (!(await isBranchProtected(owner, repo, defaultBranch, token))) {
|
|
537
|
-
throw err;
|
|
538
|
-
}
|
|
539
|
-
const bootstrapPR = await openBootstrapConfigPR(owner, repo, files, token, defaultBranch, autoMergeEnabled);
|
|
540
|
-
return { bootstrapPR };
|
|
541
|
-
}
|
|
415
|
+
const defaultBranch = await getDefaultBranch(owner, repo, token);
|
|
416
|
+
const bootstrapPR = await openBootstrapConfigPR(owner, repo, files, token, defaultBranch, autoMergeEnabled);
|
|
417
|
+
return { bootstrapPR };
|
|
542
418
|
}
|
|
543
419
|
const SCAN_POLL_INTERVAL_MS = 1500;
|
|
544
420
|
// A run still marked `running` but whose KV state hasn't been touched for this
|
|
@@ -864,8 +740,6 @@ async function stepSelectRepos(token) {
|
|
|
864
740
|
const PR_RULES_PATH = '.haystack/pr-rules.yml';
|
|
865
741
|
const REVIEW_POLICY_PATH = '.haystack/review-policy.md';
|
|
866
742
|
const HAYSTACK_CONFIG_PATH = '.haystack.json';
|
|
867
|
-
const RULES_COMMIT_MSG = 'chore: configure Haystack rules via CLI setup wizard';
|
|
868
|
-
const POLICY_COMMIT_MSG = 'chore: add review policies via CLI setup wizard';
|
|
869
743
|
/**
|
|
870
744
|
* Scan each selected repo with one unified scan and collect the proposed
|
|
871
745
|
* rules / policies / instructions per repo. A failed scan for one repo records
|
|
@@ -1093,13 +967,13 @@ function buildOnboardingFiles(result, autoMerge) {
|
|
|
1093
967
|
const files = [];
|
|
1094
968
|
const prRules = buildPrRules(result.rules);
|
|
1095
969
|
if (prRules.length > 0) {
|
|
1096
|
-
files.push({ path: PR_RULES_PATH, content: serializePrRulesYaml(prRules)
|
|
970
|
+
files.push({ path: PR_RULES_PATH, content: serializePrRulesYaml(prRules) });
|
|
1097
971
|
}
|
|
1098
972
|
const reviewPolicyMd = buildReviewPolicyMarkdown(result.policies, result.instructions);
|
|
1099
973
|
if (reviewPolicyMd) {
|
|
1100
|
-
files.push({ path: REVIEW_POLICY_PATH, content: reviewPolicyMd
|
|
974
|
+
files.push({ path: REVIEW_POLICY_PATH, content: reviewPolicyMd });
|
|
1101
975
|
}
|
|
1102
|
-
files.push({ path: HAYSTACK_CONFIG_PATH, content: buildHaystackConfigJson(autoMerge)
|
|
976
|
+
files.push({ path: HAYSTACK_CONFIG_PATH, content: buildHaystackConfigJson(autoMerge) });
|
|
1103
977
|
return files;
|
|
1104
978
|
}
|
|
1105
979
|
async function stepConfirm(byRepo, token) {
|
|
@@ -1146,24 +1020,17 @@ async function stepConfirm(byRepo, token) {
|
|
|
1146
1020
|
const result = byRepo.get(repoFullName);
|
|
1147
1021
|
const files = buildOnboardingFiles(result, autoMerge);
|
|
1148
1022
|
try {
|
|
1149
|
-
ui.progress(`
|
|
1023
|
+
ui.progress(`Opening Configure Haystack PR for ${repoFullName}...`);
|
|
1150
1024
|
const writeResult = await writeOnboardingFilesToRepo(owner, repo, files, autoMerge, token);
|
|
1151
1025
|
ui.clearProgress();
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
: ' (default branch protected — opened a PR for you to review and merge)';
|
|
1161
|
-
console.log(chalk.green(` ✓ ${repoFullName}`) + chalk.dim(note));
|
|
1162
|
-
console.log(chalk.dim(` ${writeResult.bootstrapPR.prUrl}`));
|
|
1163
|
-
}
|
|
1164
|
-
else {
|
|
1165
|
-
console.log(chalk.green(` ✓ ${repoFullName}`));
|
|
1166
|
-
}
|
|
1026
|
+
// Onboarding always opens a PR (never a direct commit). With auto-merge on,
|
|
1027
|
+
// Haystack merges it once its labels are processed; with auto-merge off it
|
|
1028
|
+
// waits for the user. Step 7 ensures .entire/settings.json rode along in
|
|
1029
|
+
// the same commit.
|
|
1030
|
+
bootstrapPRs.set(repoFullName, writeResult.bootstrapPR);
|
|
1031
|
+
const note = autoMerge ? ' (opened a PR)' : ' (opened a PR for you to review and merge)';
|
|
1032
|
+
console.log(chalk.green(` ✓ ${repoFullName}`) + chalk.dim(note));
|
|
1033
|
+
console.log(chalk.dim(` ${writeResult.bootstrapPR.prUrl}`));
|
|
1167
1034
|
}
|
|
1168
1035
|
catch (err) {
|
|
1169
1036
|
ui.clearProgress();
|
|
@@ -1242,11 +1109,11 @@ async function runSetupFlow(options) {
|
|
|
1242
1109
|
ui.result({ status: 'cancelled' });
|
|
1243
1110
|
return;
|
|
1244
1111
|
}
|
|
1245
|
-
// Step 7: Install Entire CLI (session tracking). bootstrapPRs tells it
|
|
1246
|
-
//
|
|
1247
|
-
// .entire/settings.json
|
|
1248
|
-
// --skip-entire skips the whole step (binary install
|
|
1249
|
-
//
|
|
1112
|
+
// Step 7: Install Entire CLI (session tracking). bootstrapPRs tells it which
|
|
1113
|
+
// repos got a Configure Haystack PR (all of them) so it can report that
|
|
1114
|
+
// .entire/settings.json rode along in that PR's commit.
|
|
1115
|
+
// --skip-entire skips the whole step (binary install + hooks) — not just the
|
|
1116
|
+
// install prompt.
|
|
1250
1117
|
if (options.skipEntire) {
|
|
1251
1118
|
console.log(chalk.dim(' Skipping session tracking (--skip-entire).\n'));
|
|
1252
1119
|
}
|
|
@@ -1344,53 +1211,6 @@ async function installEntireBinary() {
|
|
|
1344
1211
|
return false;
|
|
1345
1212
|
}
|
|
1346
1213
|
}
|
|
1347
|
-
/**
|
|
1348
|
-
* Write .entire/settings.json directly to the default branch.
|
|
1349
|
-
*
|
|
1350
|
-
* This is the DIRECT path only. Repos whose default branch is protected
|
|
1351
|
-
* never reach here — step 6's openBootstrapConfigPR already bundled
|
|
1352
|
-
* .entire/settings.json into the bootstrap PR's single commit (on a ~ALL
|
|
1353
|
-
* ruleset you can't add it as a later commit, so it must ride along in the
|
|
1354
|
-
* branch-creation commit). stepInstallEntire skips this call for those
|
|
1355
|
-
* repos. So if we're here, step 6 proved the default branch accepts a
|
|
1356
|
-
* direct commit.
|
|
1357
|
-
*/
|
|
1358
|
-
async function configureEntireSettingsViaAPI(repoFullName, token) {
|
|
1359
|
-
const [owner, repo] = repoFullName.split('/');
|
|
1360
|
-
const result = await buildEntireSettingsContent(repoFullName, token);
|
|
1361
|
-
// null → file already exists unchanged; nothing to write.
|
|
1362
|
-
if (result === null)
|
|
1363
|
-
return;
|
|
1364
|
-
// buildEntireSettingsContent already fetched the file — reuse its
|
|
1365
|
-
// content + existingSha. A second GET here would (a) be redundant and
|
|
1366
|
-
// (b) on a transient non-404 failure fall back to existingSha=null,
|
|
1367
|
-
// turning the PUT into a create that GitHub rejects 422 — masking the
|
|
1368
|
-
// real read error. (Non-404 read failures already threw upstream.)
|
|
1369
|
-
const { content, existingSha } = result;
|
|
1370
|
-
const headers = ghHeaders(token);
|
|
1371
|
-
const body = {
|
|
1372
|
-
message: 'chore: configure Entire CLI settings',
|
|
1373
|
-
content: Buffer.from(content).toString('base64'),
|
|
1374
|
-
};
|
|
1375
|
-
if (existingSha)
|
|
1376
|
-
body.sha = existingSha;
|
|
1377
|
-
const putResp = await fetch(`${GITHUB_PROXY}/repos/${owner}/${repo}/contents/.entire/settings.json`, {
|
|
1378
|
-
method: 'PUT',
|
|
1379
|
-
headers,
|
|
1380
|
-
body: JSON.stringify(body),
|
|
1381
|
-
});
|
|
1382
|
-
if (!putResp.ok) {
|
|
1383
|
-
const errText = await putResp.text().catch(() => '');
|
|
1384
|
-
const error = new Error(`GitHub API ${putResp.status}: ${errText}`);
|
|
1385
|
-
trackError('entire_settings_write_failed', {
|
|
1386
|
-
error_message: error.message,
|
|
1387
|
-
repo: repoFullName,
|
|
1388
|
-
status: putResp.status,
|
|
1389
|
-
had_existing: !!existingSha,
|
|
1390
|
-
});
|
|
1391
|
-
throw error;
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
1214
|
async function stepInstallEntire(selectedRepos, token, bootstrapPRs) {
|
|
1395
1215
|
console.log(chalk.bold('\n Step 7: Session tracking (Entire CLI)\n'));
|
|
1396
1216
|
const status = isEntireInstalled();
|
|
@@ -1429,48 +1249,28 @@ async function stepInstallEntire(selectedRepos, token, bootstrapPRs) {
|
|
|
1429
1249
|
if (!userConsented) {
|
|
1430
1250
|
console.log(chalk.dim(' Skipping session tracking.\n'));
|
|
1431
1251
|
}
|
|
1432
|
-
//
|
|
1433
|
-
//
|
|
1434
|
-
//
|
|
1435
|
-
// step 6, which ALREADY bundled .entire/settings.json into that PR's
|
|
1436
|
-
// single commit (on a ~ALL ruleset it can't be added as a later commit).
|
|
1437
|
-
// So we skip those here and only direct-write the non-protected repos.
|
|
1252
|
+
// Onboarding always opens a PR, and .entire/settings.json rides along in that
|
|
1253
|
+
// PR's single commit (see openBootstrapConfigPR), so there's nothing to write
|
|
1254
|
+
// separately here — just report what landed.
|
|
1438
1255
|
if (userConsented) {
|
|
1439
|
-
let configured = 0;
|
|
1440
1256
|
let bundled = 0;
|
|
1441
1257
|
for (const repoFullName of selectedRepos) {
|
|
1442
1258
|
const bootstrapPR = bootstrapPRs.get(repoFullName);
|
|
1443
|
-
if (bootstrapPR) {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
console.log(chalk.green(` ✓ ${repoFullName}`) + chalk.dim(' (in bootstrap PR)'));
|
|
1447
|
-
bundled++;
|
|
1448
|
-
}
|
|
1449
|
-
else {
|
|
1450
|
-
// The bootstrap PR shipped the config files but not
|
|
1451
|
-
// .entire/settings.json — its optional read failed transiently. Don't
|
|
1452
|
-
// count it as configured; the warning was already shown when the PR
|
|
1453
|
-
// was opened.
|
|
1454
|
-
console.log(chalk.yellow(` ⚠ ${repoFullName}`) +
|
|
1455
|
-
chalk.dim(' — bootstrap PR has config files only; re-run setup to add session tracking'));
|
|
1456
|
-
}
|
|
1457
|
-
continue;
|
|
1259
|
+
if (bootstrapPR?.entireSettingsBundled) {
|
|
1260
|
+
console.log(chalk.green(` ✓ ${repoFullName}`) + chalk.dim(' (in PR)'));
|
|
1261
|
+
bundled++;
|
|
1458
1262
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
console.log(chalk.
|
|
1464
|
-
|
|
1465
|
-
}
|
|
1466
|
-
catch (err) {
|
|
1467
|
-
ui.clearProgress();
|
|
1468
|
-
console.log(chalk.yellow(` ⚠ ${repoFullName}: ${err instanceof Error ? err.message : err}`));
|
|
1263
|
+
else {
|
|
1264
|
+
// The PR shipped the config files but not .entire/settings.json — its
|
|
1265
|
+
// optional read failed transiently. The warning was already shown when
|
|
1266
|
+
// the PR was opened.
|
|
1267
|
+
console.log(chalk.yellow(` ⚠ ${repoFullName}`) +
|
|
1268
|
+
chalk.dim(' — PR has config files only; re-run setup to add session tracking'));
|
|
1469
1269
|
}
|
|
1470
1270
|
}
|
|
1471
|
-
if (
|
|
1472
|
-
console.log(chalk.green(`\n ✓ Session tracking configured on ${
|
|
1473
|
-
trackSetupEvent('entire_configured', { configured:
|
|
1271
|
+
if (bundled > 0) {
|
|
1272
|
+
console.log(chalk.green(`\n ✓ Session tracking configured on ${bundled} repo(s) (transcripts auto-compacted)\n`));
|
|
1273
|
+
trackSetupEvent('entire_configured', { configured: bundled, total: selectedRepos.length });
|
|
1474
1274
|
}
|
|
1475
1275
|
else {
|
|
1476
1276
|
console.log(chalk.yellow('\n ⚠ Could not configure session tracking on any repos.\n'));
|
package/dist/utils/prompter.js
CHANGED
|
@@ -164,8 +164,8 @@ class JsonPrompter {
|
|
|
164
164
|
if (!obj || typeof obj !== 'object')
|
|
165
165
|
return;
|
|
166
166
|
// Mirror OpenCode: question replies are keyed by `requestID`, permission
|
|
167
|
-
// replies by `permissionID`.
|
|
168
|
-
const key = (obj.requestID ?? obj.permissionID
|
|
167
|
+
// replies by `permissionID`.
|
|
168
|
+
const key = (obj.requestID ?? obj.permissionID);
|
|
169
169
|
if (typeof key !== 'string')
|
|
170
170
|
return;
|
|
171
171
|
const resolve = this.pending.get(key);
|
|
@@ -199,7 +199,7 @@ class JsonPrompter {
|
|
|
199
199
|
// string "false" isn't coerced truthy.
|
|
200
200
|
if (reply.reject)
|
|
201
201
|
throw new PromptCancelledError(q.id);
|
|
202
|
-
return toBool(reply.answers
|
|
202
|
+
return toBool(reply.answers);
|
|
203
203
|
}
|
|
204
204
|
async multiselect(q) {
|
|
205
205
|
if (q.id in this.answers)
|
|
@@ -215,7 +215,7 @@ class JsonPrompter {
|
|
|
215
215
|
// than silently coercing to [] (PR001: no silent fallback).
|
|
216
216
|
if (reply.reject)
|
|
217
217
|
throw new PromptCancelledError(q.id);
|
|
218
|
-
return asArray(reply.answers
|
|
218
|
+
return asArray(reply.answers, q.id);
|
|
219
219
|
}
|
|
220
220
|
async action(q) {
|
|
221
221
|
if (this.answers[q.id] === 'skip')
|
|
@@ -232,8 +232,8 @@ class JsonPrompter {
|
|
|
232
232
|
let override = null;
|
|
233
233
|
this.ensureStdin();
|
|
234
234
|
const answerPromise = this.awaitReply(q.id).then((reply) => {
|
|
235
|
-
const r =
|
|
236
|
-
override = r === 'reject'
|
|
235
|
+
const r = reply.response;
|
|
236
|
+
override = r === 'reject' ? 'skip' : 'done';
|
|
237
237
|
});
|
|
238
238
|
try {
|
|
239
239
|
while (Date.now() - started < timeout) {
|