@bridge_gpt/mcp-server 0.2.28 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/build/agent-registry.js +5 -0
- package/build/conductor-bin.js +1 -1
- package/build/index.js +13 -12
- package/build/install-bridge.js +165 -32
- package/build/readme.generated.js +1 -1
- package/build/start-tickets.js +19 -9
- package/build/version.generated.js +1 -1
- package/package.json +1 -1
package/build/install-bridge.js
CHANGED
|
@@ -57,10 +57,23 @@
|
|
|
57
57
|
* repository prompt asks for a new project name rather than an existing
|
|
58
58
|
* registration (see `RepoNamePromptMode`).
|
|
59
59
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
60
|
+
* ORDINARY-ENTRY INVITE RECLASSIFICATION (BAPI-661) — the have-key branch's own
|
|
61
|
+
* credential input (`--api-key`, `BAPI_API_KEY`, or its hidden prompt) is
|
|
62
|
+
* CREDENTIAL-AGNOSTIC: it accepts a full API key OR a bootstrap-invite token,
|
|
63
|
+
* because a user handed only an invite has no way to know it belongs in a
|
|
64
|
+
* different flag. `classifyEnteredCredential` inspects the resolved value
|
|
65
|
+
* immediately after input resolution (before repository-name resolution); a
|
|
66
|
+
* `bapi_inv_…` value flips the branch's `bootstrapInviteMode`/`inviteToken` and
|
|
67
|
+
* the run falls into the SAME bootstrap-invite protocol described below, verbatim.
|
|
68
|
+
* Explicit `--invite`/`BAPI_INVITE` and `--email`/`BAPI_SIGNUP_EMAIL` are never
|
|
69
|
+
* reclassified — they are already unambiguous and remain authoritative.
|
|
70
|
+
*
|
|
71
|
+
* BOOTSTRAP-INVITE MODE (BAPI-606) — one of the exceptions to "this command
|
|
72
|
+
* consumes a key, it does not create one" (the ordinary-entry reclassification
|
|
73
|
+
* above is the other). With `--invite` (or `BAPI_INVITE`, or a reclassified
|
|
74
|
+
* ordinary-entry value) there is no API key yet, so the pre-flight ping of Step 2
|
|
75
|
+
* CANNOT be made: the exchange is what mints the key, and it REPLACES that ping.
|
|
76
|
+
* The order becomes:
|
|
64
77
|
*
|
|
65
78
|
* resolve bootstrap-invite token (no-echo prompt by DEFAULT — the delivered
|
|
66
79
|
* one-liner is secret-free) → resolve repo name → scaffold → generate
|
|
@@ -185,11 +198,15 @@ export function getInstallBridgeUsage() {
|
|
|
185
198
|
"an interactive terminal keeps the existing deterministic behavior and no prompt.",
|
|
186
199
|
"",
|
|
187
200
|
"Inputs (the only two irreducible ones):",
|
|
188
|
-
" --api-key <key> Bridge API key. Falls back to the
|
|
189
|
-
" then an interactive (no-echo) prompt.
|
|
190
|
-
" Bridge API web UI Security page — this
|
|
191
|
-
" key, it does not create one
|
|
192
|
-
"
|
|
201
|
+
" --api-key <key> Bridge API key OR bootstrap invite. Falls back to the",
|
|
202
|
+
" BAPI_API_KEY env var, then an interactive (no-echo) prompt.",
|
|
203
|
+
" Generate a key in the Bridge API web UI Security page — this",
|
|
204
|
+
" command consumes a key, it does not create one. A value",
|
|
205
|
+
" detected as a bootstrap invite (bapi_inv_…), from any of the",
|
|
206
|
+
" three sources above, is instead redeemed to CREATE a new",
|
|
207
|
+
" project and its first admin key — the same as --invite —",
|
|
208
|
+
" so it skips repository lookup entirely (--email and --invite",
|
|
209
|
+
" remain the explicit, preferred entry points for a new project).",
|
|
193
210
|
" NEVER printed or logged.",
|
|
194
211
|
" --repo <name> Repository name. --repo and BAPI_REPO_NAME still take",
|
|
195
212
|
" priority and short-circuit before any network call. When",
|
|
@@ -710,8 +727,16 @@ export function createDefaultInstallBridgeDeps() {
|
|
|
710
727
|
};
|
|
711
728
|
}
|
|
712
729
|
/**
|
|
713
|
-
* Resolve the
|
|
714
|
-
* prompt. Fails (secret-free) when none is available and
|
|
730
|
+
* Resolve the ordinary credential entry: `--api-key` → `BAPI_API_KEY` env →
|
|
731
|
+
* interactive no-echo prompt. Fails (secret-free) when none is available and
|
|
732
|
+
* stdin is non-interactive.
|
|
733
|
+
*
|
|
734
|
+
* Despite the name, this is CREDENTIAL-AGNOSTIC (BAPI-661): the returned value may
|
|
735
|
+
* be a full Bridge API key or a bootstrap-invite token (`bapi_inv_…`) — the caller
|
|
736
|
+
* classifies it with {@link classifyEnteredCredential} immediately after this
|
|
737
|
+
* resolves and routes an invite into the existing redemption workflow. This
|
|
738
|
+
* function itself performs no classification; it only resolves which raw string
|
|
739
|
+
* was entered.
|
|
715
740
|
*/
|
|
716
741
|
export async function resolveApiKey(options, deps) {
|
|
717
742
|
if (typeof options.apiKey === "string" && options.apiKey.trim().length > 0) {
|
|
@@ -722,16 +747,20 @@ export async function resolveApiKey(options, deps) {
|
|
|
722
747
|
return { ok: true, value: fromEnv.trim() };
|
|
723
748
|
}
|
|
724
749
|
if (deps.isTTY && deps.promptSecret) {
|
|
725
|
-
const entered = (await deps.promptSecret("Bridge API key (input hidden): ")).trim();
|
|
750
|
+
const entered = (await deps.promptSecret("Bridge API key or invite (input hidden): ")).trim();
|
|
726
751
|
if (entered.length > 0) {
|
|
727
752
|
return { ok: true, value: entered };
|
|
728
753
|
}
|
|
729
|
-
return {
|
|
754
|
+
return {
|
|
755
|
+
ok: false,
|
|
756
|
+
error: "No Bridge API key or invite entered. Pass --api-key, set the BAPI_API_KEY environment " +
|
|
757
|
+
"variable, or try the hidden prompt again.",
|
|
758
|
+
};
|
|
730
759
|
}
|
|
731
760
|
return {
|
|
732
761
|
ok: false,
|
|
733
|
-
error: "
|
|
734
|
-
"(no interactive terminal is available to prompt for it).",
|
|
762
|
+
error: "A Bridge API key or invite is required. Pass --api-key or set the BAPI_API_KEY " +
|
|
763
|
+
"environment variable (no interactive terminal is available to prompt for it).",
|
|
735
764
|
};
|
|
736
765
|
}
|
|
737
766
|
/**
|
|
@@ -836,8 +865,8 @@ export function resolveInstallBridgeOnboardingBranch(options, env) {
|
|
|
836
865
|
return { kind: "need-key", method: "self-serve" };
|
|
837
866
|
return { kind: "have-key" };
|
|
838
867
|
}
|
|
839
|
-
/** The exact visible text of the bare-TTY onboarding selector (BAPI-626). */
|
|
840
|
-
export const INSTALL_BRIDGE_KEY_SELECTOR_PROMPT = "Do you have a Bridge API key? [Y/n] ";
|
|
868
|
+
/** The exact visible text of the bare-TTY onboarding selector (BAPI-626, BAPI-661). */
|
|
869
|
+
export const INSTALL_BRIDGE_KEY_SELECTOR_PROMPT = "Do you have a Bridge API key or invite? [Y/n] ";
|
|
841
870
|
/**
|
|
842
871
|
* Interactive wrapper around {@link resolveInstallBridgeOnboardingBranch}.
|
|
843
872
|
*
|
|
@@ -1000,7 +1029,8 @@ export function promptMultiSelectViaReadline(promptText, options, defaults, inpu
|
|
|
1000
1029
|
const mark = selected.has(opt.id) ? "[x]" : "[ ]";
|
|
1001
1030
|
output.write(` ${idx + 1}. ${mark} ${opt.label}\n`);
|
|
1002
1031
|
});
|
|
1003
|
-
output.write("Type numbers to toggle, e.g. 1,3 — then Enter
|
|
1032
|
+
output.write("Type numbers to toggle, e.g. 1,3 — then Enter.\n");
|
|
1033
|
+
output.write("Press Enter on an empty line to confirm — re-typing a number un-selects it.\n");
|
|
1004
1034
|
output.write("Enter numbers to toggle (comma-separated), or press Enter to accept: ");
|
|
1005
1035
|
};
|
|
1006
1036
|
const rl = readline.createInterface({ input, output });
|
|
@@ -1195,20 +1225,41 @@ export async function chooseInstallBridgeLaunchAgent(agents, deps) {
|
|
|
1195
1225
|
* completes.
|
|
1196
1226
|
*
|
|
1197
1227
|
* Condition-specific warning prefixes are added by the caller, never inside here.
|
|
1228
|
+
*
|
|
1229
|
+
* `toolLabels` (BAPI-661) names the actually-selected platform(s) in the
|
|
1230
|
+
* `"configured"` message so "open it in an AI coding tool" becomes "open it in
|
|
1231
|
+
* GitHub Copilot (VS Code)" when that is what was selected — an empty array (the
|
|
1232
|
+
* explicit empty-selection call site, or any caller with nothing to name) falls
|
|
1233
|
+
* back to the prior generic "an AI coding tool" wording unchanged.
|
|
1198
1234
|
*/
|
|
1199
|
-
export function buildManualInstallBridgeContinuation(kind) {
|
|
1235
|
+
export function buildManualInstallBridgeContinuation(kind, toolLabels) {
|
|
1200
1236
|
if (kind === "empty-selection") {
|
|
1201
1237
|
return [
|
|
1202
1238
|
"No AI coding tools were configured, so nothing was set up for this project.",
|
|
1203
1239
|
"Re-run install-bridge and select at least one tool to configure it.",
|
|
1204
1240
|
].join("\n");
|
|
1205
1241
|
}
|
|
1242
|
+
const toolPhrase = formatToolLabelPhrase(toolLabels);
|
|
1206
1243
|
return [
|
|
1207
|
-
|
|
1244
|
+
`To finish configuring this project, open it in ${toolPhrase} that has the`,
|
|
1208
1245
|
"Bridge MCP server configured and run /install-bridge.",
|
|
1209
1246
|
"Until the project is configured, your Bridge MCP tools stay limited.",
|
|
1210
1247
|
].join("\n");
|
|
1211
1248
|
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Format 0, 1, or many tool labels into a readable phrase for the continuation
|
|
1251
|
+
* message. Empty falls back to the generic "an AI coding tool"; one label is used
|
|
1252
|
+
* bare; two or more join with a serial (Oxford) comma and "and".
|
|
1253
|
+
*/
|
|
1254
|
+
function formatToolLabelPhrase(labels) {
|
|
1255
|
+
if (labels.length === 0)
|
|
1256
|
+
return "an AI coding tool";
|
|
1257
|
+
if (labels.length === 1)
|
|
1258
|
+
return labels[0];
|
|
1259
|
+
if (labels.length === 2)
|
|
1260
|
+
return `${labels[0]} and ${labels[1]}`;
|
|
1261
|
+
return `${labels.slice(0, -1).join(", ")}, and ${labels[labels.length - 1]}`;
|
|
1262
|
+
}
|
|
1212
1263
|
/** The exact consent-prompt prefix, shared with tests so the copy cannot drift. */
|
|
1213
1264
|
export const INSTALL_BRIDGE_LAUNCH_CONSENT_PROMPT_PREFIX = "Bridge can configure and set up this project for you automatically. Open a ";
|
|
1214
1265
|
/**
|
|
@@ -1287,6 +1338,17 @@ function hostConfigTargetsForPlatforms(platforms) {
|
|
|
1287
1338
|
.filter((t) => t.scope === "project" && t.format === "json")
|
|
1288
1339
|
.map((t) => ({ relPath: t.relPath, topLevelKey: t.topLevelKey }));
|
|
1289
1340
|
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Derive the human-facing labels for a set of selected platforms, in registry
|
|
1343
|
+
* (`HOST_PLATFORM_ORDER`) order rather than the caller's input order, so a
|
|
1344
|
+
* continuation message names selected tools deterministically (BAPI-661).
|
|
1345
|
+
* Deduplicates by platform ID and reads every label from `MCP_HOST_TARGETS` —
|
|
1346
|
+
* never a second, hand-maintained label map.
|
|
1347
|
+
*/
|
|
1348
|
+
export function labelsForPlatforms(platforms) {
|
|
1349
|
+
const set = new Set(platforms);
|
|
1350
|
+
return HOST_PLATFORM_ORDER.filter((id) => set.has(id)).map((id) => MCP_HOST_TARGETS[id].label);
|
|
1351
|
+
}
|
|
1290
1352
|
/** Resolve which project-local host configs to write, mirroring runInit detection. */
|
|
1291
1353
|
async function resolveHostConfigTargets(deps) {
|
|
1292
1354
|
const targets = [
|
|
@@ -1447,9 +1509,19 @@ export function buildPingUrl(baseUrl, repoName) {
|
|
|
1447
1509
|
return url.toString();
|
|
1448
1510
|
}
|
|
1449
1511
|
/**
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1512
|
+
* Fixed, secret-free fallback for an HTTP 403 whose body carries no usable
|
|
1513
|
+
* `detail` (absent, malformed, unreadable, or a non-string `detail`). Deliberately
|
|
1514
|
+
* does NOT claim the key is invalid or expired — a 403 means the credential was
|
|
1515
|
+
* understood but denied access, which is a different (and often more actionable)
|
|
1516
|
+
* failure than an invalid/expired key.
|
|
1517
|
+
*/
|
|
1518
|
+
const CONNECTIVITY_ACCESS_DENIED_FALLBACK = "The Bridge API denied access to this repository (HTTP 403). Verify the repo_name and that this " +
|
|
1519
|
+
"credential is authorized for it.";
|
|
1520
|
+
/**
|
|
1521
|
+
* Verify connectivity. Distinguishes a rejected/expired credential (401) from a
|
|
1522
|
+
* repository-specific access denial (403, BAPI-661) from an unknown repo /
|
|
1523
|
+
* not-found (404) where the response allows. The key is sent in the `X-API-Key`
|
|
1524
|
+
* header and NEVER appears in any returned message.
|
|
1453
1525
|
*/
|
|
1454
1526
|
export async function verifyConnectivity(deps, baseUrl, repoName, apiKey) {
|
|
1455
1527
|
const url = buildPingUrl(baseUrl, repoName);
|
|
@@ -1474,7 +1546,7 @@ export async function verifyConnectivity(deps, baseUrl, repoName, apiKey) {
|
|
|
1474
1546
|
}
|
|
1475
1547
|
if (resp.ok)
|
|
1476
1548
|
return { ok: true };
|
|
1477
|
-
if (resp.status === 401
|
|
1549
|
+
if (resp.status === 401) {
|
|
1478
1550
|
return {
|
|
1479
1551
|
ok: false,
|
|
1480
1552
|
message: `The Bridge API rejected the credential (HTTP ${resp.status}). The API key may be invalid or expired ` +
|
|
@@ -1482,6 +1554,27 @@ export async function verifyConnectivity(deps, baseUrl, repoName, apiKey) {
|
|
|
1482
1554
|
"permission error.)",
|
|
1483
1555
|
};
|
|
1484
1556
|
}
|
|
1557
|
+
if (resp.status === 403) {
|
|
1558
|
+
// BAPI-661: a 403 means the credential was understood but access to THIS repo
|
|
1559
|
+
// was denied — the server's `detail` (e.g. "repository '<name>' is not
|
|
1560
|
+
// registered; verify the repo_name") is a safe, specific, non-secret message
|
|
1561
|
+
// that is far more actionable than the blanket invalid-key diagnosis 401 uses.
|
|
1562
|
+
// Only a nonblank string `detail` is ever surfaced; any other shape (missing
|
|
1563
|
+
// body, invalid JSON, a non-string/blank detail, or a body-read exception)
|
|
1564
|
+
// collapses to the fixed fallback so nothing unvalidated ever reaches the user.
|
|
1565
|
+
let detail;
|
|
1566
|
+
try {
|
|
1567
|
+
const body = (await resp.json());
|
|
1568
|
+
detail = body?.detail;
|
|
1569
|
+
}
|
|
1570
|
+
catch {
|
|
1571
|
+
return { ok: false, message: CONNECTIVITY_ACCESS_DENIED_FALLBACK };
|
|
1572
|
+
}
|
|
1573
|
+
if (typeof detail === "string" && detail.trim().length > 0) {
|
|
1574
|
+
return { ok: false, message: detail.trim() };
|
|
1575
|
+
}
|
|
1576
|
+
return { ok: false, message: CONNECTIVITY_ACCESS_DENIED_FALLBACK };
|
|
1577
|
+
}
|
|
1485
1578
|
if (resp.status === 404) {
|
|
1486
1579
|
return {
|
|
1487
1580
|
ok: false,
|
|
@@ -1651,6 +1744,21 @@ export async function exchangeBootstrapInvite(deps, baseUrl, token, repoName, ke
|
|
|
1651
1744
|
* treated as a `failed` mint rather than fed into the redemption path.
|
|
1652
1745
|
*/
|
|
1653
1746
|
export const BOOTSTRAP_INVITE_TOKEN_PREFIX = "bapi_inv_";
|
|
1747
|
+
/**
|
|
1748
|
+
* Classify a value entered through the ORDINARY credential input (`--api-key`,
|
|
1749
|
+
* `BAPI_API_KEY`, or the have-key hidden prompt) as a bootstrap-invite token or a
|
|
1750
|
+
* full API key (BAPI-661). This lets a user who was handed an invite but reaches
|
|
1751
|
+
* for the ordinary key input still get redeemed correctly, instead of the invite
|
|
1752
|
+
* being sent as an (invalid) API key to repository resolution / connectivity.
|
|
1753
|
+
*
|
|
1754
|
+
* Pure and total: trims first, then does a case-sensitive prefix check against
|
|
1755
|
+
* {@link BOOTSTRAP_INVITE_TOKEN_PREFIX}. Blank and every non-matching value is
|
|
1756
|
+
* `"api-key"` — the existing resolvers already reject a blank value, so this
|
|
1757
|
+
* function does not need to special-case it. Never logs or returns the value.
|
|
1758
|
+
*/
|
|
1759
|
+
export function classifyEnteredCredential(value) {
|
|
1760
|
+
return value.trim().startsWith(BOOTSTRAP_INVITE_TOKEN_PREFIX) ? "invite" : "api-key";
|
|
1761
|
+
}
|
|
1654
1762
|
/** The self-serve mint endpoint: `<base>/setup/bootstrap/self-serve`. No query string — ever. */
|
|
1655
1763
|
export function buildSelfServeMintUrl(baseUrl) {
|
|
1656
1764
|
return `${baseUrl.replace(/\/+$/, "")}/setup/bootstrap/self-serve`;
|
|
@@ -1996,8 +2104,14 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
1996
2104
|
return 1;
|
|
1997
2105
|
}
|
|
1998
2106
|
const branch = branchResult.branch;
|
|
1999
|
-
|
|
2000
|
-
|
|
2107
|
+
// Mutable (BAPI-661): an ordinary have-key credential that classifies as a
|
|
2108
|
+
// bootstrap invite (see the have-key arm below) flips `bootstrapInviteMode` to
|
|
2109
|
+
// true AFTER branch selection, before repository-name resolution — everything
|
|
2110
|
+
// downstream that branches on these two flags then follows the existing
|
|
2111
|
+
// bootstrap-invite protocol automatically. Explicit `--invite`/`BAPI_INVITE` and
|
|
2112
|
+
// `--email`/`BAPI_SIGNUP_EMAIL` never pass through this reclassification.
|
|
2113
|
+
let bootstrapInviteMode = branch.kind === "need-key";
|
|
2114
|
+
let selfServeSignupMode = branch.kind === "need-key" && branch.method === "self-serve";
|
|
2001
2115
|
// ---- Resolve inputs (may prompt when interactive) ----
|
|
2002
2116
|
// Resolve the credential/entry input first so a fully-empty non-interactive
|
|
2003
2117
|
// invocation fails with the (more relevant) missing-input message before the repo
|
|
@@ -2030,7 +2144,20 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
2030
2144
|
errorLog(`Error: ${keyResult.error}`);
|
|
2031
2145
|
return 1;
|
|
2032
2146
|
}
|
|
2033
|
-
|
|
2147
|
+
// BAPI-661: an invite pasted into the ordinary credential input (flag, env, or
|
|
2148
|
+
// hidden prompt) is redeemed like an explicit --invite, not sent as an API key
|
|
2149
|
+
// to repository resolution / connectivity. Reclassifying BEFORE that
|
|
2150
|
+
// resolution is what lets the rest of the function reuse the existing
|
|
2151
|
+
// bootstrap-invite protocol verbatim.
|
|
2152
|
+
if (classifyEnteredCredential(keyResult.value) === "invite") {
|
|
2153
|
+
inviteToken = keyResult.value.trim();
|
|
2154
|
+
apiKey = "";
|
|
2155
|
+
bootstrapInviteMode = true;
|
|
2156
|
+
selfServeSignupMode = false;
|
|
2157
|
+
}
|
|
2158
|
+
else {
|
|
2159
|
+
apiKey = keyResult.value;
|
|
2160
|
+
}
|
|
2034
2161
|
}
|
|
2035
2162
|
// baseUrl is resolved BEFORE repository input because the have-key branch needs
|
|
2036
2163
|
// it for the server-resolution request.
|
|
@@ -2077,7 +2204,11 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
2077
2204
|
else {
|
|
2078
2205
|
// Feature-detect + degrade: 404 (old server), 409 (unresolved/ambiguous/
|
|
2079
2206
|
// client-scoped), and network/other errors all fall back to the existing
|
|
2080
|
-
// local resolution WITHOUT a cause-specific message or leaked detail
|
|
2207
|
+
// local resolution — WITHOUT a cause-specific message or leaked detail, but
|
|
2208
|
+
// (BAPI-661) WITH a visible signal that the fallback is happening, so a
|
|
2209
|
+
// guessed name is never confirmed silently as if it had been verified.
|
|
2210
|
+
log("Couldn't auto-resolve your repo from the key; falling back to a guessed name — confirm " +
|
|
2211
|
+
"it matches the setup UI.");
|
|
2081
2212
|
const repoResult = await resolveRepoName(options, deps, "existing-registration");
|
|
2082
2213
|
if (!repoResult.ok) {
|
|
2083
2214
|
errorLog(`Error: ${repoResult.error}`);
|
|
@@ -2159,7 +2290,9 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
2159
2290
|
// Return BEFORE any config/credential write, materialization, prewarm, or spawn, so a
|
|
2160
2291
|
// bare `--tools=` (or an EOF-resolved picker) cleanly does nothing but guide a re-run.
|
|
2161
2292
|
if (planLaunch.kind === "manual" && planLaunch.reason === "empty-selection") {
|
|
2162
|
-
|
|
2293
|
+
// Explicit empty selection: no labels to name, so the generic fallback is
|
|
2294
|
+
// intentional here — pass [] rather than `labelsForPlatforms(selectedPlatforms)`.
|
|
2295
|
+
log(buildManualInstallBridgeContinuation("empty-selection", []));
|
|
2163
2296
|
return 0;
|
|
2164
2297
|
}
|
|
2165
2298
|
// ---- Finalize the launch agent for spawn-capable decisions ----
|
|
@@ -2609,7 +2742,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
2609
2742
|
// deterministic setup is complete, so this is a non-fatal warning (exit 0), with
|
|
2610
2743
|
// the same shared continuation so the remediation is consistent.
|
|
2611
2744
|
errorLog(`Warning: setup steps completed, but the agent session could not be opened (${spawnResult.error}).`);
|
|
2612
|
-
log(buildManualInstallBridgeContinuation("configured"));
|
|
2745
|
+
log(buildManualInstallBridgeContinuation("configured", labelsForPlatforms(selectedPlatforms)));
|
|
2613
2746
|
return 0;
|
|
2614
2747
|
}
|
|
2615
2748
|
log("");
|
|
@@ -2623,13 +2756,13 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
2623
2756
|
return 0;
|
|
2624
2757
|
}
|
|
2625
2758
|
// Declined (or non-TTY suppression): the deterministic setup is complete and durable.
|
|
2626
|
-
log(buildManualInstallBridgeContinuation("configured"));
|
|
2759
|
+
log(buildManualInstallBridgeContinuation("configured", labelsForPlatforms(selectedPlatforms)));
|
|
2627
2760
|
return 0;
|
|
2628
2761
|
}
|
|
2629
2762
|
// No launchable session (a no-launchable selection, or a non-TTY / declined chooser):
|
|
2630
2763
|
// finalAgentName is null, so nothing was materialized. The configured hosts are
|
|
2631
2764
|
// durable — print the shared continuation + limited-tools note (never the
|
|
2632
2765
|
// empty-selection variant, which already returned above).
|
|
2633
|
-
log(buildManualInstallBridgeContinuation("configured"));
|
|
2766
|
+
log(buildManualInstallBridgeContinuation("configured", labelsForPlatforms(selectedPlatforms)));
|
|
2634
2767
|
return 0;
|
|
2635
2768
|
}
|