@bridge_gpt/mcp-server 0.2.29 → 0.2.30

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.
@@ -150,18 +150,27 @@ export function buildPrewarmCommandPreview() {
150
150
  * (`maybe_auto_parse_if_ready`), with no prompt on any path.
151
151
  */
152
152
  export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command in the install-spawn context (tell the command it is running " +
153
- "in the install-spawn context so it SKIPS its Stage 8, Stage 9, and Stage 10 offers — this " +
154
- "session's only closing interaction is the concise capability report and learn recommendation " +
155
- "below). " +
156
- "Complete the command's read-after-write concise capability report first: the single 'What Bridge " +
153
+ "in the install-spawn context so it SKIPS its Stage 8, Stage 9, and Stage 10 offers). " +
154
+ "The command chooses its own mode from the install manifest's `configured` flag do NOT force a " +
155
+ "mode: when `configured` is false it runs its fresh-configuration flow, and when `configured` is " +
156
+ "true it runs JOIN MODE. In JOIN MODE the command must NOT derive, approve, apply, or offer any " +
157
+ "configuration and applies zero fields for any caller; a joining member's only closing interaction " +
158
+ "is the welcome plus the concise capability report (no /learn-repository prompt for a member). " +
159
+ "Complete the command's concise capability report: the single 'What Bridge " +
157
160
  "can help with' section, rendered exactly as the server's concise_tool_capabilities field gives it " +
158
161
  "(Regularly useful, then Occasionally useful, each tool's display_name only, plus each tier's " +
159
162
  "server-computed '+N more' where non-zero). Do not render the obsolete five-section report, and do " +
160
163
  "not locally filter, count, or fall back to the complete tool_capabilities catalog. " +
164
+ "The install-spawn skip set is exactly Stage 8, Stage 9, and Stage 10. The command-owned Stage 11 " +
165
+ "(invite teammates) is NOT in that skip set: it is independently gated and may run only when the " +
166
+ "caller's role is admin AND customer_type is b2b AND an interactive response is available; " +
167
+ "otherwise it is skipped silently. If Stage 11 mints a teammate key, show that plaintext key " +
168
+ "exactly once and never repeat it in any summary, retry, or diagnostic. " +
161
169
  "Do NOT ask any indexing question, call parse_repository, mention /parse-repository, or claim " +
162
170
  "indexing has started or is pending — indexing is decided entirely by the server-side readiness " +
163
171
  "funnel with no visibility from this session, so say nothing about it at all. " +
164
- "After the capability report, recommend /learn-repository as the next step: explain briefly that it " +
172
+ "After the capability report on the fresh-configuration path, recommend /learn-repository as the " +
173
+ "next step: explain briefly that it " +
165
174
  "learns repository-specific architecture, review, testing, correctness, and validation-manual " +
166
175
  "configuration by researching the actual codebase. Do NOT run /learn-repository yourself — only " +
167
176
  "recommend it; running it is the human's next explicit invocation. " +
@@ -169,9 +178,11 @@ export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command
169
178
  "integration's own configure_in pointer, verbatim. The pointer is per-integration and is NOT " +
170
179
  "always the setup UI: GitHub's is a terminal command (connect-github), while Jira, SFCC, and " +
171
180
  "Bitbucket point at the setup UI. Follow whatever the report says rather than assuming. " +
172
- "End with an explicit summary line stating how many config fields the apply_install_manifest call " +
181
+ "On the fresh-configuration path, end with an explicit summary line stating how many config fields " +
182
+ "the apply_install_manifest call " +
173
183
  "applied (e.g. 'Applied 8 of 9 derived fields') — if 0 fields were applied, say so loudly and " +
174
- "explain what is still pending.";
184
+ "explain what is still pending. In JOIN MODE there is no apply — state instead that the project " +
185
+ "was already configured and zero changes were made, without a fabricated applied count.";
175
186
  /** Default base URL when `BAPI_BASE_URL` is unset (mirrors index.ts). */
176
187
  export const DEFAULT_BAPI_BASE_URL = "https://bridgegpt-api.com";
177
188
  /** Default docs dir when `BAPI_DOCS_DIR` is unset (mirrors index.ts). */
@@ -185,10 +196,13 @@ export function getInstallBridgeUsage() {
185
196
  "One-command Bridge API project bootstrap. Scaffolds the project, writes the",
186
197
  "per-host MCP config with your credentials, verifies connectivity, persists the",
187
198
  "routing credential, then — on a TTY, only after a Y/N consent prompt — opens a",
188
- "fresh session in your selected tool to derive the remaining config, present a",
189
- "concise capability report, and recommend /learn-repository. Indexing is never",
190
- "asked about it starts automatically once the repository reaches full parse",
191
- "readiness.",
199
+ "fresh session in your selected tool. If the project is not yet configured it",
200
+ "derives the remaining config, presents a concise capability report, and",
201
+ "recommends /learn-repository. If the project is already configured it instead",
202
+ "joins you to it without proposing or applying any changes and just shows the",
203
+ "concise capability report (a b2b admin is additionally offered a teammate-invite",
204
+ "step). So not every run applies config fields. Indexing is never asked about —",
205
+ "it starts automatically once the repository reaches full parse readiness.",
192
206
  "",
193
207
  "Run it bare — `install-bridge` with no flags — in a terminal and it asks",
194
208
  `\`${INSTALL_BRIDGE_KEY_SELECTOR_PROMPT.trim()}\` first. Answer yes (or press Enter) for the`,
@@ -261,10 +275,12 @@ export function getInstallBridgeUsage() {
261
275
  ` ${HOST_PLATFORM_ORDER.join(", ")}.`,
262
276
  " Both --tools=claude-code,codex and",
263
277
  " --tools claude-code,codex are accepted. On an",
264
- " interactive terminal WITHOUT this flag the",
265
- " checklist starts EMPTY no tool is pre-selected",
266
- " (not even Claude Code) and you must select at",
267
- " least one. A non-interactive (non-TTY) run without",
278
+ " interactive terminal WITHOUT this flag you get a",
279
+ " numbered picker: enter one or more tool numbers,",
280
+ " comma-separated (e.g. 1,3), and press Enter once ",
281
+ " no tool is pre-selected (not even Claude Code) and",
282
+ " you must select at least one. A non-interactive",
283
+ " (non-TTY) run without",
268
284
  " --tools writes the legacy automatic set (Claude",
269
285
  " Code plus any detected Cursor / Copilot VS Code)",
270
286
  " and never opens an agent terminal (it prints",
@@ -1009,88 +1025,78 @@ export async function resolveRepoName(options, deps, mode = "existing-registrati
1009
1025
  /** A per-host MCP config target (mirrors runInit's configTargets shape). */
1010
1026
  /** Stable wording of the interactive tool-selection prompt (BAPI-635). */
1011
1027
  export const INSTALL_BRIDGE_TOOL_SELECTOR_PROMPT = "Which AI coding tools do you use on this project?";
1028
+ /** A one-based selection token is exactly one or more digits, no sign/decimal/leading-zero. */
1029
+ const SELECTION_TOKEN_PATTERN = /^[1-9][0-9]*$/;
1012
1030
  /**
1013
- * Interactive numbered multi-select prompt on stderr (TTY only). Displays a
1014
- * legend and usage line plus each option with a checked/unchecked marker seeded
1015
- * from `defaults`, accepts a comma-separated list of numbers that TOGGLE the rows
1016
- * and RE-PROMPT (acceptance is a separate bare Enter), and accepts only a NON-EMPTY
1017
- * selection on a bare Enter — an empty one prints "Select at least one tool." and
1018
- * re-prompts (AC-1/AC-3). Reprints on an invalid token rather than corrupting
1019
- * state. Resolves safely (to the current set, including an empty one) on EOF /
1020
- * synchronous close so a readline regression can never hang or discard the answer.
1031
+ * Interactive single-shot numbered picker on stderr (TTY only). Renders the
1032
+ * heading and each option exactly once, then accepts ONE strict
1033
+ * comma-separated line of one-based numbers and resolves immediately — there
1034
+ * is no preselected/toggle state and no separate confirmation step (BAPI-663).
1035
+ *
1036
+ * A blank line or a line containing any invalid token (non-numeric, zero,
1037
+ * signed, leading-zero, decimal, out-of-range) is rejected atomically the
1038
+ * whole line is discarded, one error line is printed, and the picker
1039
+ * re-prompts without re-rendering the option list. A valid line is
1040
+ * deduplicated and returned in `options` order regardless of entry order.
1041
+ *
1042
+ * Resolves to `[]` on EOF / synchronous close before any line has been
1043
+ * committed, so a readline regression can never hang. Returns `[]`
1044
+ * immediately, without creating a readline interface, when `options` is
1045
+ * empty.
1021
1046
  */
1022
- export function promptMultiSelectViaReadline(promptText, options, defaults, input = process.stdin, output = process.stderr) {
1047
+ export function promptMultiSelectViaReadline(promptText, options, input = process.stdin, output = process.stderr) {
1048
+ if (options.length === 0)
1049
+ return Promise.resolve([]);
1023
1050
  return new Promise((resolve) => {
1024
- const selected = new Set(defaults);
1025
- const render = () => {
1026
- output.write(`\n${promptText}\n`);
1027
- output.write("[x] = selected · [ ] = not selected\n");
1028
- options.forEach((opt, idx) => {
1029
- const mark = selected.has(opt.id) ? "[x]" : "[ ]";
1030
- output.write(` ${idx + 1}. ${mark} ${opt.label}\n`);
1031
- });
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");
1034
- output.write("Enter numbers to toggle (comma-separated), or press Enter to accept: ");
1035
- };
1036
1051
  const rl = readline.createInterface({ input, output });
1037
- let answered = false;
1038
- const finish = () => {
1039
- answered = true;
1052
+ let settled = false;
1053
+ const finish = (result) => {
1054
+ if (settled)
1055
+ return;
1056
+ settled = true;
1040
1057
  rl.close();
1041
- resolve(options.filter((o) => selected.has(o.id)).map((o) => o.id));
1058
+ resolve(result);
1042
1059
  };
1043
1060
  // EOF / synchronous close must resolve rather than deadlock the top-level
1044
- // await; `answered` guards the synchronous close from discarding a real answer.
1045
- rl.on("close", () => {
1046
- if (!answered)
1047
- resolve(options.filter((o) => selected.has(o.id)).map((o) => o.id));
1061
+ // await; the `settled` guard stops a synchronous `close` (fired by `rl.close()`
1062
+ // above) from replacing an already-committed answer with the EOF fallback.
1063
+ rl.on("close", () => finish([]));
1064
+ output.write(`\n${promptText}\n`);
1065
+ options.forEach((opt, idx) => {
1066
+ output.write(` ${idx + 1}. ${opt.label}\n`);
1048
1067
  });
1049
- const ask = () => {
1050
- render();
1051
- rl.question("", (answer) => {
1068
+ output.write("Enter the number(s) of the tools you use (e.g. 1,3), then Enter.\n");
1069
+ const prompt = () => {
1070
+ rl.question("> ", (answer) => {
1052
1071
  const trimmed = answer.trim();
1053
1072
  if (trimmed.length === 0) {
1054
- // AC-3: a bare Enter accepts ONLY a non-empty selection; an empty one
1055
- // re-prompts rather than resolving to nothing. EOF/close (handled above)
1056
- // still resolves the current — possibly empty — set so input can never hang.
1057
- if (selected.size === 0) {
1058
- output.write("Select at least one tool.\n");
1059
- ask();
1060
- return;
1061
- }
1062
- finish();
1073
+ output.write("Select at least one tool.\n");
1074
+ prompt();
1063
1075
  return;
1064
1076
  }
1065
1077
  const tokens = trimmed.split(",").map((t) => t.trim());
1066
- const nums = [];
1067
- let bad = false;
1078
+ const indices = [];
1068
1079
  for (const tok of tokens) {
1080
+ if (!SELECTION_TOKEN_PATTERN.test(tok)) {
1081
+ output.write(`Invalid selection. Enter numbers between 1 and ${options.length}.\n`);
1082
+ prompt();
1083
+ return;
1084
+ }
1069
1085
  const n = Number(tok);
1070
- if (!Number.isInteger(n) || n < 1 || n > options.length) {
1071
- bad = true;
1072
- break;
1086
+ if (n < 1 || n > options.length) {
1087
+ output.write(`Invalid selection. Enter numbers between 1 and ${options.length}.\n`);
1088
+ prompt();
1089
+ return;
1073
1090
  }
1074
- nums.push(n);
1075
- }
1076
- if (bad) {
1077
- output.write(`Invalid selection. Enter numbers between 1 and ${options.length}.\n`);
1078
- ask();
1079
- return;
1080
- }
1081
- for (const n of nums) {
1082
- const opt = options[n - 1];
1083
- if (selected.has(opt.id))
1084
- selected.delete(opt.id);
1085
- else
1086
- selected.add(opt.id);
1091
+ indices.push(n);
1087
1092
  }
1088
- // Toggling re-renders the updated checklist and re-prompts; acceptance is a
1089
- // separate, deliberate bare Enter (AC-1/AC-3) rather than an implicit accept.
1090
- ask();
1093
+ const chosen = new Set(indices);
1094
+ finish(options
1095
+ .filter((_, idx) => chosen.has(idx + 1))
1096
+ .map((o) => o.id));
1091
1097
  });
1092
1098
  };
1093
- ask();
1099
+ prompt();
1094
1100
  });
1095
1101
  }
1096
1102
  /**
@@ -1110,13 +1116,11 @@ export async function resolveSelectedHostPlatforms(deps, options) {
1110
1116
  }
1111
1117
  const ctx = await buildDetectionContext(deps);
1112
1118
  const detected = new Set(detectDefaultPlatforms(ctx));
1113
- // 2. Interactive multi-select on a TTY. AC-2: no row is pre-selected — the
1114
- // picker seeds an EMPTY default set (dropping the former forced Claude Code
1115
- // inclusion and detected-platform seeding) and requires an explicit selection.
1119
+ // 2. Interactive single-shot numbered picker on a TTY. No row is pre-selected
1120
+ // — the picker has no default state and requires an explicit selection.
1116
1121
  if (deps.isTTY && deps.promptMultiSelect) {
1117
1122
  const optionList = allHostTargets().map((t) => ({ id: t.id, label: t.label }));
1118
- const defaults = [];
1119
- const chosen = await deps.promptMultiSelect(INSTALL_BRIDGE_TOOL_SELECTOR_PROMPT, optionList, defaults);
1123
+ const chosen = await deps.promptMultiSelect(INSTALL_BRIDGE_TOOL_SELECTOR_PROMPT, optionList);
1120
1124
  return HOST_PLATFORM_ORDER.filter((id) => chosen.includes(id));
1121
1125
  }
1122
1126
  // 3. Legacy non-TTY automatic set: Claude + detected Cursor / Copilot VS Code.